mirror of
https://github.com/SleepingInsomniac/pixelfaucet
synced 2025-01-15 15:41:08 +01:00
Refactor fill_triangle, add textured example
This commit is contained in:
parent
2f65ce7de3
commit
44935fcbcc
2 changed files with 187 additions and 128 deletions
|
@ -3,6 +3,7 @@ require "../src/controller"
|
||||||
require "../src/sprite"
|
require "../src/sprite"
|
||||||
require "../src/pixel"
|
require "../src/pixel"
|
||||||
require "../src/vector"
|
require "../src/vector"
|
||||||
|
require "../src/sprite"
|
||||||
|
|
||||||
require "../src/3d/*"
|
require "../src/3d/*"
|
||||||
|
|
||||||
|
@ -10,19 +11,25 @@ class ThreeDee < PF::Game
|
||||||
@projector : PF::Projector
|
@projector : PF::Projector
|
||||||
@camera : PF::Camera
|
@camera : PF::Camera
|
||||||
@paused = false
|
@paused = false
|
||||||
@speed = 5.0
|
@speed = 10.0
|
||||||
@controller : PF::Controller(PF::Keys)
|
@controller : PF::Controller(PF::Keys)
|
||||||
|
@depth_buffer : PF::DepthBuffer
|
||||||
|
|
||||||
def initialize(*args, **kwargs)
|
def initialize(*args, **kwargs)
|
||||||
super
|
super
|
||||||
|
|
||||||
@projector = PF::Projector.new(width, height)
|
@projector = PF::Projector.new(width, height)
|
||||||
|
@depth_buffer = PF::DepthBuffer.new(width, height)
|
||||||
|
|
||||||
@camera = @projector.camera
|
@camera = @projector.camera
|
||||||
# @model = PF::Mesh.load_obj("./assets/cube.obj")
|
|
||||||
@model = PF::Mesh.load_obj("./assets/pixelfaucet.obj")
|
@model = PF::Mesh.load_obj("./assets/pixelfaucet.obj")
|
||||||
@texture = PF::Sprite.new("./assets/bricks.png")
|
|
||||||
@model.position.z = @model.position.z + 2.0
|
@model.position.z = @model.position.z + 2.0
|
||||||
|
|
||||||
|
@cube_model = PF::Mesh.load_obj("./assets/cube.obj")
|
||||||
|
@cube_model.position.z = @cube_model.position.z + 2.5
|
||||||
|
@sprite = PF::Sprite.new("./assets/bricks.png")
|
||||||
|
|
||||||
@controller = PF::Controller(PF::Keys).new({
|
@controller = PF::Controller(PF::Keys).new({
|
||||||
PF::Keys::RIGHT => "Rotate Right",
|
PF::Keys::RIGHT => "Rotate Right",
|
||||||
PF::Keys::LEFT => "Rotate Left",
|
PF::Keys::LEFT => "Rotate Left",
|
||||||
|
@ -60,22 +67,23 @@ class ThreeDee < PF::Game
|
||||||
@camera.position.y = @camera.position.y - @speed * dt
|
@camera.position.y = @camera.position.y - @speed * dt
|
||||||
end
|
end
|
||||||
|
|
||||||
# Controll the camera pitch instead of elevation -
|
# Control the camera pitch instead of elevation -
|
||||||
|
# TODO: this needs to account for where the camera is pointing
|
||||||
|
|
||||||
# if @controller.held?("Up")
|
# if @controller.held?("Up")
|
||||||
# @camera.pitch = @camera.pitch + (@speed / 2) * dt
|
# @camera.pitch = @camera.pitch + (@speed / 5) * dt
|
||||||
# end
|
# end
|
||||||
|
#
|
||||||
# if @controller.held?("Down")
|
# if @controller.held?("Down")
|
||||||
# @camera.pitch = @camera.pitch - (@speed / 2) * dt
|
# @camera.pitch = @camera.pitch - (@speed / 5) * dt
|
||||||
# end
|
# end
|
||||||
|
|
||||||
if @controller.held?("Rotate Left")
|
if @controller.held?("Rotate Left")
|
||||||
@camera.yaw = @camera.yaw - (@speed / 2) * dt
|
@camera.yaw = @camera.yaw - (@speed / 3) * dt
|
||||||
end
|
end
|
||||||
|
|
||||||
if @controller.held?("Rotate Right")
|
if @controller.held?("Rotate Right")
|
||||||
@camera.yaw = @camera.yaw + (@speed / 2) * dt
|
@camera.yaw = @camera.yaw + (@speed / 3) * dt
|
||||||
end
|
end
|
||||||
|
|
||||||
if @controller.held?("Forward")
|
if @controller.held?("Forward")
|
||||||
|
@ -86,33 +94,53 @@ class ThreeDee < PF::Game
|
||||||
@camera.position = @camera.position - (forward * @speed * dt)
|
@camera.position = @camera.position - (forward * @speed * dt)
|
||||||
end
|
end
|
||||||
|
|
||||||
@model.rotation.x = @model.rotation.x + 3.0 * dt
|
@model.rotation.x = @model.rotation.x + 1.0 * dt
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
clear(25, 50, 25)
|
# clear(25, 50, 25)
|
||||||
tris = @projector.project(@model.tris)
|
clear
|
||||||
draw_string("Triangles: #{tris.size}", 3, 3)
|
@depth_buffer.clear
|
||||||
|
|
||||||
|
cube_tris = @projector.project(@cube_model.tris)
|
||||||
|
cube_tris.each do |tri|
|
||||||
|
fill_triangle(
|
||||||
|
tri.p1.to_i, tri.p2.to_i, tri.p3.to_i, # Points
|
||||||
|
tri.t1, tri.t2, tri.t3, # Texture Points
|
||||||
|
@sprite,
|
||||||
|
@depth_buffer,
|
||||||
|
tri.color
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
tris = @projector.project(@model.tris)
|
||||||
tris.each do |tri|
|
tris.each do |tri|
|
||||||
# Rasterize all triangles
|
# Rasterize all triangles
|
||||||
|
|
||||||
fill_triangle(
|
fill_triangle(
|
||||||
PF::Vector[tri.p1.x.to_i, tri.p1.y.to_i],
|
PF::Vector[tri.p1.x.to_i, tri.p1.y.to_i],
|
||||||
PF::Vector[tri.p2.x.to_i, tri.p2.y.to_i],
|
PF::Vector[tri.p2.x.to_i, tri.p2.y.to_i],
|
||||||
PF::Vector[tri.p3.x.to_i, tri.p3.y.to_i],
|
PF::Vector[tri.p3.x.to_i, tri.p3.y.to_i],
|
||||||
pixel: tri.color
|
pixel: tri.color # buffer: @depth_buffer
|
||||||
)
|
)
|
||||||
|
|
||||||
# draw_triangle(
|
|
||||||
# PF::Vector[tri.p1.x.to_i, tri.p1.y.to_i],
|
|
||||||
# PF::Vector[tri.p2.x.to_i, tri.p2.y.to_i],
|
|
||||||
# PF::Vector[tri.p3.x.to_i, tri.p3.y.to_i],
|
|
||||||
# pixel: PF::Pixel.blue
|
|
||||||
# )
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
string = String.build do |io|
|
||||||
|
io << "Triangles: " << tris.size + cube_tris.size
|
||||||
|
io << "\nPosition: "
|
||||||
|
io << "x: " << @camera.position.x.round(2)
|
||||||
|
io << "y: " << @camera.position.y.round(2)
|
||||||
|
io << "z: " << @camera.position.z.round(2)
|
||||||
|
io << "\nRotation: "
|
||||||
|
io << "x: " << @camera.rotation.x.round(2)
|
||||||
|
io << "y: " << @camera.rotation.y.round(2)
|
||||||
|
io << "z: " << @camera.rotation.z.round(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
draw_string(string, 3, 3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# engine = ThreeDee.new(256, 240, 4)
|
# engine = ThreeDee.new(256, 240, 4)
|
||||||
engine = ThreeDee.new(640, 480, 2)
|
engine = ThreeDee.new(256 * 2, 240 * 2, 2)
|
||||||
engine.run!
|
engine.run!
|
||||||
|
|
|
@ -2,12 +2,25 @@ require "../line"
|
||||||
|
|
||||||
module PF
|
module PF
|
||||||
class Sprite
|
class Sprite
|
||||||
# Draw a filled in triangle
|
private def sort_verticies(p1 : Vector2, p2 : Vector2, p3 : Vector2)
|
||||||
def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new)
|
|
||||||
# Sort points from top to bottom
|
# Sort points from top to bottom
|
||||||
p1, p2 = p2, p1 if p2.y < p1.y
|
p1, p2 = p2, p1 if p2.y < p1.y
|
||||||
p1, p3 = p3, p1 if p3.y < p1.y
|
p1, p3 = p3, p1 if p3.y < p1.y
|
||||||
p2, p3 = p3, p2 if p3.y < p2.y
|
p2, p3 = p3, p2 if p3.y < p2.y
|
||||||
|
{p1, p2, p3}
|
||||||
|
end
|
||||||
|
|
||||||
|
private def sort_verticies(p1 : Vector3, p2 : Vector3, p3 : Vector3, t1 : Vector3, t2 : Vector3, t3 : Vector3)
|
||||||
|
# Sort points from top to bottom
|
||||||
|
p1, p2, t1, t2 = p2, p1, t2, t1 if p2.y < p1.y
|
||||||
|
p1, p3, t1, t3 = p3, p1, t3, t1 if p3.y < p1.y
|
||||||
|
p2, p3, t2, t3 = p3, p2, t3, t2 if p3.y < p2.y
|
||||||
|
{p1, p2, p3, t1, t2, t3}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Draw a filled in triangle
|
||||||
|
def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new)
|
||||||
|
p1, p2, p3 = sort_verticies(p1, p2, p3)
|
||||||
|
|
||||||
# sort left and right edges by run / rise
|
# sort left and right edges by run / rise
|
||||||
line_left = PF::Line.new(p1, p2)
|
line_left = PF::Line.new(p1, p2)
|
||||||
|
@ -21,36 +34,46 @@ module PF
|
||||||
slope_left = line_left.slope
|
slope_left = line_left.slope
|
||||||
slope_right = line_right.slope
|
slope_right = line_right.slope
|
||||||
|
|
||||||
c = p1.y # offset
|
offset = p1.y # height offset from 0
|
||||||
height = p3.y - p1.y
|
height = p3.y - p1.y # height of the triangle
|
||||||
mid = p2.y - p1.y
|
mid = p2.y - p1.y # where the flat bottom triangle ends
|
||||||
|
|
||||||
0.upto(height) do |y|
|
start = 0
|
||||||
if slope_left == 0
|
fin = mid
|
||||||
# When there is no rise, set the x value directly
|
|
||||||
x_left = line_left.p2.x
|
|
||||||
else
|
|
||||||
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
|
||||||
end
|
|
||||||
|
|
||||||
if slope_right == 0
|
# Draw the triangle in two halfs
|
||||||
x_right = line_right.p2.x
|
# 0 - Flat bottom triangle
|
||||||
else
|
# 1 - Flat top triangle
|
||||||
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
2.times do |half|
|
||||||
end
|
start.upto(fin) do |y|
|
||||||
|
if slope_left == 0
|
||||||
x_left.upto(x_right) do |x|
|
# When there is no rise, set the x value directly
|
||||||
draw_point(x, y + c, pixel)
|
x_left = line_left.p2.x
|
||||||
end
|
|
||||||
|
|
||||||
if y == mid
|
|
||||||
if line_left.p2 == p2
|
|
||||||
line_left = PF::Line.new(p2, p3)
|
|
||||||
slope_left = line_left.slope
|
|
||||||
else
|
else
|
||||||
line_right = PF::Line.new(p2, p3)
|
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
||||||
slope_right = line_right.slope
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if slope_right == 0
|
||||||
|
x_right = line_right.p2.x
|
||||||
|
else
|
||||||
|
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
||||||
|
end
|
||||||
|
|
||||||
|
x_left.upto(x_right) do |x|
|
||||||
|
draw_point(x, y + offset, pixel)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
start = fin + 1
|
||||||
|
fin = height
|
||||||
|
|
||||||
|
# Depending on which point is the middle
|
||||||
|
if line_left.p2 == p2
|
||||||
|
line_left = PF::Line.new(p2, p3)
|
||||||
|
slope_left = line_left.slope
|
||||||
|
else
|
||||||
|
line_right = PF::Line.new(p2, p3)
|
||||||
|
slope_right = line_right.slope
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -61,11 +84,11 @@ module PF
|
||||||
end
|
end
|
||||||
|
|
||||||
# Draw a textured triangle
|
# Draw a textured triangle
|
||||||
def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, t1 : Vector3, t2 : Vector3, t3 : Vector3, sprite : Sprite, buffer : DepthBuffer, color : Pixel = Pixel.white)
|
def fill_triangle(p1 : Vector3, p2 : Vector3, p3 : Vector3, t1 : Vector3, t2 : Vector3, t3 : Vector3, sprite : Sprite, buffer : DepthBuffer, color : Pixel = Pixel::White)
|
||||||
# Sort points from top to bottom
|
p1, p2, p3, t1, t2, t3 = sort_verticies(p1, p2, p3, t1, t2, t3)
|
||||||
p1, p2, t1, t2 = p2, p1, t2, t1 if p2.y < p1.y
|
|
||||||
p1, p3, t1, t3 = p3, p1, t3, t1 if p3.y < p1.y
|
# z = (p1.z + p2.z + p3.z) // 3
|
||||||
p2, p3, t2, t3 = p3, p2, t3, t2 if p3.y < p2.y
|
z = p1.z
|
||||||
|
|
||||||
# Create lines starting at p1 to the other lower points
|
# Create lines starting at p1 to the other lower points
|
||||||
line_left = PF::Line.new(p1, p2)
|
line_left = PF::Line.new(p1, p2)
|
||||||
|
@ -92,95 +115,103 @@ module PF
|
||||||
height = p3.y - p1.y # triangle height
|
height = p3.y - p1.y # triangle height
|
||||||
mid = p2.y - p1.y # where the shorter line ends
|
mid = p2.y - p1.y # where the shorter line ends
|
||||||
|
|
||||||
# Starting at 0, up to the height, draw scanlines
|
start = 0
|
||||||
0.upto(height) do |y|
|
fin = mid
|
||||||
# Get the normalized t value for this height level
|
|
||||||
ty = height > 0 ? y / height : 0.0
|
|
||||||
|
|
||||||
# Check if the slope is 0, this would cause a divide by 0
|
# Draw the triangle in two halfs
|
||||||
if slope_left == 0
|
# 0 - Flat bottom triangle
|
||||||
# When there is no rise, set the x value directly
|
# 1 - Flat top triangle
|
||||||
x_left = line_left.p2.x
|
2.times do |half|
|
||||||
else
|
start.upto(fin) do |y|
|
||||||
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
# Check if the slope is 0, this would cause a divide by 0
|
||||||
end
|
if slope_left == 0
|
||||||
|
# When there is no rise, set the x value directly
|
||||||
if slope_right == 0
|
x_left = line_left.p2.x
|
||||||
x_right = line_right.p2.x
|
|
||||||
t_right = tl_right.p2.x
|
|
||||||
else
|
|
||||||
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the normalized t value for this height level
|
|
||||||
ty = height > 0 ? y / height : 0.0
|
|
||||||
|
|
||||||
# LERP both texture edges at the y position to create a new line
|
|
||||||
tyl =
|
|
||||||
if switch_left
|
|
||||||
# Line left is the 2 part segment
|
|
||||||
if y <= mid
|
|
||||||
# still in the first segment (percent over the midpoint)
|
|
||||||
mid == 0 ? 0.0 : y / mid
|
|
||||||
else
|
|
||||||
# in the second part, pecentage of middle to end
|
|
||||||
height == 0 ? 0.0 : (y - mid) / (height - mid)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
height == 0 ? 0.0 : y / height
|
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
||||||
end
|
end
|
||||||
|
|
||||||
tyr =
|
if slope_right == 0
|
||||||
unless switch_left
|
x_right = line_right.p2.x
|
||||||
if y <= mid
|
t_right = tl_right.p2.x
|
||||||
mid == 0 ? 1.0 : y / mid
|
|
||||||
else
|
|
||||||
height == 0 ? 1.0 : (y - mid) / (height - mid)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
height == 0 ? 1.0 : y / height
|
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
||||||
end
|
end
|
||||||
|
|
||||||
texture_line = PF::Line.new(tl_left.lerp(tyl), tl_right.lerp(tyr))
|
# Get the normalized t value for this height level
|
||||||
|
ty = height > 0 ? y / height : 0.0
|
||||||
|
|
||||||
# Get the width of the scan line
|
# LERP both texture edges at the y position to create a new line
|
||||||
scan_size = x_right - x_left
|
tyl =
|
||||||
|
if switch_left
|
||||||
|
# Line left is the 2 part segment
|
||||||
|
if half == 0
|
||||||
|
# still in the first segment (percent over the midpoint)
|
||||||
|
mid == 0 ? 0.0 : y / mid
|
||||||
|
else
|
||||||
|
# in the second part, pecentage of middle to end
|
||||||
|
height == 0 ? 0.0 : (y - mid) / (height - mid)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
height == 0 ? 0.0 : y / height
|
||||||
|
end
|
||||||
|
|
||||||
x_left.upto(x_right) do |x|
|
tyr =
|
||||||
# LERP the line between the texture edges
|
unless switch_left
|
||||||
t = scan_size == 0 ? 0.0 : (x - x_left) / scan_size
|
if half == 0
|
||||||
texture_point = texture_line.lerp(t)
|
mid == 0 ? 1.0 : y / mid
|
||||||
|
else
|
||||||
|
height == 0 ? 1.0 : (y - mid) / (height - mid)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
height == 0 ? 1.0 : y / height
|
||||||
|
end
|
||||||
|
|
||||||
if texture_point.z >= buffer[x, y + c]
|
texture_line = PF::Line.new(tl_left.lerp(tyl), tl_right.lerp(tyr))
|
||||||
buffer[x, y + c] = texture_point.z
|
|
||||||
# Get the x and y of the texture coords, divide by z for perspective, then
|
|
||||||
# multiply the point by the size of the sprite to get the final texture point
|
|
||||||
sample_point = ((Vector[texture_point.x, texture_point.y] / texture_point.z) * sprite.size)
|
|
||||||
# Invert the y axis for the sprite
|
|
||||||
sample_point.y = sprite.height - sample_point.y
|
|
||||||
# sample_point = sample_point / texture_point.z if texture_point.z != 0
|
|
||||||
pixel = sprite.sample((sample_point + 0.5).to_i)
|
|
||||||
|
|
||||||
# Blend the pixel sample with the provided color
|
# Get the width of the scan line
|
||||||
pixel.r = (pixel.r * (color.r / 255)).to_u8
|
scan_size = x_right - x_left
|
||||||
pixel.g = (pixel.g * (color.g / 255)).to_u8
|
|
||||||
pixel.b = (pixel.b * (color.b / 255)).to_u8
|
|
||||||
|
|
||||||
draw_point(x, y + c, pixel)
|
x_left.upto(x_right) do |x|
|
||||||
|
# LERP the line between the texture edges
|
||||||
|
t = scan_size == 0 ? 0.0 : (x - x_left) / scan_size
|
||||||
|
texture_point = texture_line.lerp(t)
|
||||||
|
|
||||||
|
if texture_point.z > buffer[x, y + c]
|
||||||
|
buffer[x, y + c] = texture_point.z
|
||||||
|
# Get the x and y of the texture coords, divide by z for perspective, then
|
||||||
|
# multiply the point by the size of the sprite to get the final texture point
|
||||||
|
sample_point = ((Vector[texture_point.x, texture_point.y] / texture_point.z) * sprite.size)
|
||||||
|
# Invert the y axis for the sprite
|
||||||
|
sample_point.y = sprite.height - sample_point.y
|
||||||
|
sample_point %= sprite.size
|
||||||
|
|
||||||
|
pixel = sprite.sample((sample_point).to_i)
|
||||||
|
|
||||||
|
# Blend the pixel sample with the provided color
|
||||||
|
pixel = pixel.darken(color)
|
||||||
|
|
||||||
|
# Darken by distance
|
||||||
|
d = (((50.0 - z) / 100.0) + 0.5).clamp(0.0..1.0)
|
||||||
|
pixel *= d
|
||||||
|
|
||||||
|
draw_point(x, y + c, pixel)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
start = fin + 1
|
||||||
|
fin = height
|
||||||
|
|
||||||
# Once we hit the point where a line changes, we need a new slope for that line
|
# Once we hit the point where a line changes, we need a new slope for that line
|
||||||
if y == mid
|
if switch_left
|
||||||
if switch_left
|
line_left = PF::Line.new(p2, p3)
|
||||||
line_left = PF::Line.new(p2, p3)
|
tl_left = PF::Line.new(t2, t3)
|
||||||
tl_left = PF::Line.new(t2, t3)
|
slope_left = line_left.slope
|
||||||
slope_left = line_left.slope
|
else
|
||||||
else
|
line_right = PF::Line.new(p2, p3)
|
||||||
line_right = PF::Line.new(p2, p3)
|
tl_right = PF::Line.new(t2, t3)
|
||||||
tl_right = PF::Line.new(t2, t3)
|
slope_right = line_right.slope
|
||||||
slope_right = line_right.slope
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue