Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Last active October 31, 2018 19:27
Show Gist options
  • Save Heimdell/f403d9f0582a1aac0fd1ac61d10f7fe4 to your computer and use it in GitHub Desktop.
Save Heimdell/f403d9f0582a1aac0fd1ac61d10f7fe4 to your computer and use it in GitHub Desktop.
class Point < Struct.new :x, :y, :z
end
class Space < Struct.new :atoms
def [](point)
atoms[point]
end
def []=(point, material)
atoms[point] = material
end
end
class Camera < Struct.new :position
end
class RayCast < Struct.new :camera
def ray(dx, dy)
DownRay.new dx + camera.position.x, dy + camera.position.y, camera.position.z
end
end
class DownRay < Struct.new :x, :y, :z
def next
if @resource.nil? then
@point = Point.new x, y, z
@resource = 32
end
if @resource.positive? then
@resource -= 1
@point.z -= 1
@point
end
end
end
class Buffer < Struct.new :screen, :width, :height
def [](point)
screen[point]
end
def []=(point, material)
screen[point] = material
end
end
class Point2D < Struct.new :x, :y
end
class BasicRenderer
def self.render(space, camera, buffer)
raycast = RayCast.new camera
(0...buffer.width).each do |x|
(0...buffer.height).each do |y|
ray = raycast.ray(x, y)
while point = ray.next do
mat = space[point]
buffer.screen[Point2D.new x, y] = mat unless mat.nil?
end
end
end
buffer
end
end
buf = Buffer.new({}, 10, 10)
space = Space.new({ Point.new(0, 0, 0) => "a" })
cam = Camera.new Point.new(-5, -5, 5)
puts BasicRenderer.render(space, cam, buf)

What

Renderer and basis for physics and lighting.

Overview

Entities

class Space, for any Material:
    constructor(location : Location, generate : Location -> Space) : Space

    this[Point]: Material
    this[Point] = Material

class Location(origin : Point, edge : int)
    # edge is a _logarithm_ of size, since shl is a fast op

class Point(x, y, z : int)
    
class Camera(position : Point, rotation : Rotation)
    
class RayEmitter(camera : Camera):
    getRay(Point2D): Sequence<Point>
    
class Buffer, for any Material:
    this[x, y : int]: Material
    this[x, y : int] = Material
    
class Render<Material>:
    render(buffer : Buffer<Material>) : void

So, thats how the rendering works: 0) we have

  • ray emitter (base impl will shoot rays in parallel, creating isometry or 1:7:8)
  • some space (for now it will be a thin wrapper around Octree)
  • camera position (will ignore rotation for now, only take position into account
  • lightning space? (do we, or we store lightning in "Material" itself? how to update it?)
  1. For each point in buffer, we ask ray emitter to generate a ray a) For each point in the ray, we retrieve this point's material and add to accumulator. If the accumulator saturates (alpha >= 0.99? alpha >= 0.8?), we stop. b) Later, I will introduce a level-of-detail optimisation to not go into atoms that aren't woth a pixel or just too far. c) We put accumulator into the buffer.
  2. We render the buffer.

Space

For start, I'll probably will use 3D array of material as Space. Read things below only if you want some programming math to be thrown into your face.

For production, I've chose to implement space as an Octree.

Pros:

  1. Memory efficiency. We, basically will only generate and store only atoms we can see - the surface, and if there is a tight pack of similar atoms somewhere, they will be structurally optimised.

Cons:

  1. May be not effective in terms of speed. It is the tree, so we will have n cache jumps trying to render a cube of size 2^n. I have an idea how to optimise this, reusing C++ std::vector; however, it should be tested. (Also this optimisation will help to obtain fast save/load).
  2. As we don't generate what we cannot see, the physics for these objects will not work until we see them. We can, however, implement an algorithm that forces generation in some sphere (or cube) around the point (in parallel) to start actually simulating things before we see them, but thats the story for another time.

Octree problems:

  1. There are 2 kinds of Octree impls - mutable one and immutable one:
    • mutable
      • Pros:
        • Simple => Fast in terms of access.
      • Cons:
        • Probably can't save/render in the same time you mutating the tree.
        • if implemented with help of std::vector, you surely can't do anything while mutating the tree.
    • immutable:
      • Pros:
        • You have a tree, and then you save, render and mutate in parallel. No synchronisation/locks are needed. Each mutation generates new tree, which mostly refers to the old tree.
        • You can actually update the tree in multiple places for one descent into it.
        • You can update 2 branches of the tree in parallel, provided you don't go sideways between branches.
      • Cons:
        • Forget about Freeman cache.
        • The iterator is slow and complex and will copy things ALOT will only small changes done to them (can be optimised).
    • conclusion:
      • As the impls do not differ in the interface of Space, I'll probably implement both to test the speed.
      • Notice that on both impl parallel raytracing is a thing.

Space problems:

  1. We have the world in one piece. Its hard to do anything with it. So I thing actually putting "subspaces" into Space. This will work like that:

    1. We enter a Space and are trying to descent to a point1.
    2. We travel to the point until we reach a special Subspace node, which contains refs to 2 subtrees - one represeting an object and another representing the rest of the world.
    3. We go into first one and try to reach the point1. If the material received is not Air, we return it; otherwise, we go into the second subtree.

    Also, we will have index of mounted subspaces in the Space, but since this will only add to the interface of space, this all is a lesser concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment