Chapter 1 and 2

This commit is contained in:
Mirko Janssen 2022-01-23 12:07:31 +01:00
commit 5e0452ff48
18 changed files with 41905 additions and 0 deletions

1
.gitignore vendored Executable file
View file

@ -0,0 +1 @@
.idea/

8
README.md Executable file
View file

@ -0,0 +1,8 @@
# The Ray Tracer Challenge with Go
This porject is similar to my other [ray tracer repository](https://github.com/ItsAMirko/TheRayTracerChallenge). I
wanted to learn a new programming language and thought that starting with
[The Ray Tracer Challenge](https://www.amazon.de/Ray-Tracer-Challenge-Test-Driven-Renderer/dp/1680502719) by
[Jamis Buck](https://twitter.com/jamis) might be a good idea.
Don't expect me to finish the complete book but let's see how far I get this time :D

37
chapters/chapter1/main.go Executable file
View file

@ -0,0 +1,37 @@
package main
import (
"fmt"
. "raytracer/model"
)
type Projectile struct {
Position Point
Velocity Vector
}
type Environment struct {
Gravity Vector
Wind Vector
}
func (proj *Projectile) tick(env Environment) {
proj.Position = proj.Position.AddVector(proj.Velocity)
proj.Velocity = proj.Velocity.AddVector(env.Gravity).AddVector(env.Wind)
}
func main() {
startPosition := CreatePoint(0, 1, 0)
startVelocity := CreateVector(1, 1, 0).Normalize()
gravity := CreateVector(0, -0.1, 0)
wind := CreateVector(-0.01, 0, 0)
projectile := Projectile{startPosition, startVelocity}
environment := Environment{gravity, wind}
for projectile.Position.Y() > 0 {
fmt.Printf("%.4f | %.4f\n", projectile.Position.X(), projectile.Position.Y())
projectile.tick(environment)
}
}

52
chapters/chapter2/main.go Executable file
View file

@ -0,0 +1,52 @@
package main
import (
"fmt"
"math"
"os"
. "raytracer/model"
)
type Projectile struct {
Position Point
Velocity Vector
}
type Environment struct {
Gravity Vector
Wind Vector
}
func (proj *Projectile) tick(env Environment) {
proj.Position = proj.Position.AddVector(proj.Velocity)
proj.Velocity = proj.Velocity.AddVector(env.Gravity).AddVector(env.Wind)
}
func main() {
startPosition := CreatePoint(0, 1, 0)
startVelocity := CreateVector(1, 1.8, 0).Normalize().Multiply(8)
gravity := CreateVector(0, -0.05, 0)
wind := CreateVector(-0.005, 0, 0)
projectile := Projectile{startPosition, startVelocity}
environment := Environment{gravity, wind}
canvas := CreateCanvas(900, 500)
for projectile.Position.Y() > 0 {
canvas.SetPixel(uint64(math.Round(projectile.Position.X())),
uint64(500-math.Round(projectile.Position.Y())),
CreateColor(0, 1, 0))
projectile.tick(environment)
}
path, err := os.Getwd()
if err != nil {
fmt.Println(err.Error())
}
err = os.WriteFile(path+"/chapters/chapter2/test.ppm", []byte(canvas.ToPPM()), 0644)
if err != nil {
fmt.Println(err.Error())
}
}

41003
chapters/chapter2/test.ppm Executable file

File diff suppressed because it is too large Load diff

3
go.mod Executable file
View file

@ -0,0 +1,3 @@
module raytracer
go 1.17

53
model/canvas.go Executable file
View file

@ -0,0 +1,53 @@
package model
import (
"strconv"
)
type Canvas struct {
width uint64
height uint64
pixels [][]Color
}
func CreateCanvas(width uint64, height uint64) Canvas {
pixels := make([][]Color, width)
for i := uint64(0); i < width; i++ {
pixels[i] = make([]Color, height)
for j := uint64(0); j < height; j++ {
pixels[i][j] = CreateColor(0, 0, 0)
}
}
return Canvas{width: width, height: height, pixels: pixels}
}
func (c Canvas) SetPixel(x uint64, y uint64, color Color) {
if x >= c.width || y >= c.height {
return
}
c.pixels[x][y] = color
}
func (c Canvas) ToPPM() string {
header := "P3\n" +
strconv.FormatInt(int64(c.width), 10) + " " +
strconv.FormatInt(int64(c.height), 10) +
"\n255\n"
body, line := "", ""
for j := uint64(0); j < c.height; j++ {
for i := uint64(0); i < c.width; i++ {
if len(line+c.pixels[i][j].ToHexString()) > 70 {
body = body + line + "\n"
line = ""
}
line = line + c.pixels[i][j].ToHexString() + " "
}
body = body + line + "\n"
line = ""
}
return header + body
}

92
model/canvas_test.go Executable file
View file

@ -0,0 +1,92 @@
package model
import (
"strings"
"testing"
)
func TestCanCreateACanvas(t *testing.T) {
canvas := CreateCanvas(10, 20)
if canvas.width != 10 {
t.Error("Expected for width:", 10, "Got:", canvas.width)
}
if canvas.height != 20 {
t.Error("Expected for height:", 20, "Got:", canvas.height)
}
expected := CreateColor(0, 0, 0)
var actual Color
var i, j uint64
for i = 0; i < 10; i++ {
for j = 0; j < 20; j++ {
actual = canvas.pixels[i][j]
if actual.EqualTo(expected) == false {
t.Error("Iteration:", i, "Expected:", expected, "Got:", actual)
}
}
}
}
func TestCanSetAPixel(t *testing.T) {
canvas := CreateCanvas(1, 1)
expected := CreateColor(0, 0, 0)
canvas.SetPixel(0, 0, expected)
canvas.SetPixel(1, 1, expected)
actual := canvas.pixels[0][0]
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanCreatePPMFileContent(t *testing.T) {
canvas := CreateCanvas(5, 3)
canvas.SetPixel(0, 0, CreateColor(1.5, 0, 0))
canvas.SetPixel(2, 1, CreateColor(0, 0.5, 0))
canvas.SetPixel(4, 2, CreateColor(-0.5, 0, 1))
expectedArray := []string{
"P3",
"5 3",
"255",
"255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ",
"0 0 0 0 0 0 0 128 0 0 0 0 0 0 0 ",
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 ",
"",
}
actualArray := strings.Split(strings.ReplaceAll(canvas.ToPPM(), "\r\n", "\n"), "\n")
for index, actual := range actualArray {
expected := expectedArray[index]
if expected != actual {
t.Error("Index:", index, "Expected:", expected, "Got:", actual)
}
}
}
func TestRespectsMaxCharsPerLineWhenCreationPPMFileContent(t *testing.T) {
canvas := CreateCanvas(10, 1)
for x := 0; x < 10; x++ {
canvas.SetPixel(uint64(x), 0, CreateColor(1, 0.66, 0.33))
}
expectedArray := []string{
"P3",
"10 1",
"255",
"255 168 84 255 168 84 255 168 84 255 168 84 255 168 84 255 168 84 ",
"255 168 84 255 168 84 255 168 84 255 168 84 ",
"",
}
actualArray := strings.Split(strings.ReplaceAll(canvas.ToPPM(), "\r\n", "\n"), "\n")
for index, actual := range actualArray {
expected := expectedArray[index]
if expected != actual {
t.Error("Index:", index, "Expected:", expected, "Got:", actual)
}
}
}

69
model/color.go Executable file
View file

@ -0,0 +1,69 @@
package model
import (
"math"
"strconv"
)
type Color struct {
red float64
green float64
blue float64
}
func CreateColor(red float64, green float64, blue float64) Color {
return Color{red: red, green: green, blue: blue}
}
func (c Color) AddColor(c2 Color) Color {
return CreateColor(
c.red+c2.red,
c.green+c2.green,
c.blue+c2.blue)
}
func (c Color) SubtractColor(c2 Color) Color {
return CreateColor(
c.red-c2.red,
c.green-c2.green,
c.blue-c2.blue)
}
func (c Color) Multiply(multiplier float64) Color {
return CreateColor(
c.red*multiplier,
c.green*multiplier,
c.blue*multiplier)
}
func (c Color) MultiplyColor(c2 Color) Color {
return CreateColor(
c.red*c2.red,
c.green*c2.green,
c.blue*c2.blue)
}
func (c Color) ToHexString() string {
red := calcHexValue(c.red)
green := calcHexValue(c.green)
blue := calcHexValue(c.blue)
return strconv.Itoa(red) + " " + strconv.Itoa(green) + " " + strconv.Itoa(blue)
}
func (c Color) EqualTo(c2 Color) bool {
return round(c.red, 8) == round(c2.red, 8) &&
round(c.green, 8) == round(c2.green, 8) &&
round(c.blue, 8) == round(c2.blue, 8)
}
func calcHexValue(floatValue float64) int {
hex := math.Round(floatValue * 255)
if hex > 255 {
hex = 255
} else if hex < 0 {
hex = 0
}
return int(hex)
}

86
model/color_test.go Executable file
View file

@ -0,0 +1,86 @@
package model
import (
"testing"
)
func TestCanCreateAColor(t *testing.T) {
color := CreateColor(0.1, 0.2, 0.3)
if color.red != 0.1 {
t.Error("Expected for red:", 0.1, "Got:", color.red)
}
if color.green != 0.2 {
t.Error("Expected for green:", 0.2, "Got:", color.green)
}
if color.blue != 0.3 {
t.Error("Expected for blue:", 0.3, "Got:", color.blue)
}
}
func TestCanAddUpTwoColors(t *testing.T) {
color1 := CreateColor(0.9, 0.6, 0.75)
color2 := CreateColor(0.7, 0.1, 0.25)
expected := CreateColor(1.6, 0.7, 1)
actual := color1.AddColor(color2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanSubtractTwoColors(t *testing.T) {
color1 := CreateColor(0.9, 0.6, 0.75)
color2 := CreateColor(0.7, 0.1, 0.25)
expected := CreateColor(0.2, 0.5, 0.5)
actual := color1.SubtractColor(color2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanMultiplyAColorByAScalar(t *testing.T) {
color1 := CreateColor(0.2, 0.3, 0.4)
scalar := 2.0
expected := CreateColor(0.4, 0.6, 0.8)
actual := color1.Multiply(scalar)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanMultiplyAColorByAColor(t *testing.T) {
color1 := CreateColor(1, 0.2, 0.4)
color2 := CreateColor(0.9, 1, 0.1)
expected := CreateColor(0.9, 0.2, 0.04)
actual := color1.MultiplyColor(color2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanBeConvertedToHexString(t *testing.T) {
color1 := CreateColor(1, 0.5, 0)
color2 := CreateColor(-1, 0.75, 2)
expected1 := "255 128 0"
actual1 := color1.ToHexString()
if expected1 != actual1 {
t.Error("Expected:", expected1, "Got:", actual1)
}
expected2 := "0 191 255"
actual2 := color2.ToHexString()
if expected2 != actual2 {
t.Error("Expected:", expected2, "Got:", actual2)
}
}

7
model/helper.go Executable file
View file

@ -0,0 +1,7 @@
package model
import "math"
func round(valued float64, floatingPoints int) float64 {
return math.Round(valued*math.Pow10(floatingPoints)) / math.Pow10(floatingPoints)
}

30
model/helper_test.go Executable file
View file

@ -0,0 +1,30 @@
package model
import (
"testing"
)
func TestCanRoundFloatValue(t *testing.T) {
float := 1.23456789
var actual float64
var expectedValues [10]float64
expectedValues[0] = 1
expectedValues[1] = 1.2
expectedValues[2] = 1.23
expectedValues[3] = 1.235
expectedValues[4] = 1.2346
expectedValues[5] = 1.23457
expectedValues[6] = 1.234568
expectedValues[7] = 1.2345679
expectedValues[8] = 1.23456789
expectedValues[9] = 1.23456789
for i := 0; i <= 9; i++ {
actual = round(float, i)
if actual != expectedValues[i] {
t.Error("Iteration:", i, "Expected:", expectedValues[i], "Got:", actual)
}
}
}

28
model/point.go Executable file
View file

@ -0,0 +1,28 @@
package model
type Point struct {
*Tuple
}
func CreatePoint(x float64, y float64, z float64) Point {
return Point{&Tuple{x: x, y: y, z: z, w: 1}}
}
func (p Point) SubtractPoint(p2 Point) Vector {
return CreateVector(p.x-p2.x, p.y-p2.y, p.z-p2.z)
}
func (p Point) AddVector(v Vector) Point {
return CreatePoint(p.x+v.x, p.y+v.y, p.z+v.z)
}
func (p Point) SubtractVector(v Vector) Point {
return CreatePoint(p.x-v.x, p.y-v.y, p.z-v.z)
}
func (p Point) EqualTo(p2 Point) bool {
return round(p.x, 8) == round(p2.x, 8) &&
round(p.y, 8) == round(p2.y, 8) &&
round(p.z, 8) == round(p2.z, 8) &&
round(p.w, 8) == round(p2.w, 8)
}

58
model/point_test.go Executable file
View file

@ -0,0 +1,58 @@
package model
import (
"testing"
)
func TestCanCreateAPoint(t *testing.T) {
point := CreatePoint(4, -4, 3)
if point.x != 4 {
t.Error("Expected for x:", 4, "Got:", point.x)
}
if point.y != -4 {
t.Error("Expected for y:", -4, "Got:", point.y)
}
if point.z != 3 {
t.Error("Expected for z:", 3, "Got:", point.z)
}
if point.IsPoint() != true {
t.Error("Expected a point")
}
}
func TestCanSubtractTwoPoints(t *testing.T) {
point1 := CreatePoint(3, 2, 1)
point2 := CreatePoint(5, 6, 7)
expected := CreateVector(-2, -4, -6)
actual := point1.SubtractPoint(point2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanAddAVectorFromAPoint(t *testing.T) {
point1 := CreatePoint(3, -2, 1)
vector := CreateVector(1, 1, -1)
expected := CreatePoint(4, -1, 0)
actual := point1.AddVector(vector)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanSubtractAVectorFromAPoint(t *testing.T) {
point1 := CreatePoint(3, 2, 1)
vector := CreateVector(5, 6, 7)
expected := CreatePoint(-2, -4, -6)
actual := point1.SubtractVector(vector)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}

71
model/tuple.go Executable file
View file

@ -0,0 +1,71 @@
package model
type Tuple struct {
x float64
y float64
z float64
w float64
}
func (t Tuple) IsVector() bool {
return t.w == 0
}
func (t Tuple) IsPoint() bool {
return t.w == 1
}
func (t Tuple) X() float64 {
return t.x
}
func (t Tuple) Y() float64 {
return t.y
}
func (t Tuple) Z() float64 {
return t.z
}
func (t Tuple) W() float64 {
return t.w
}
func (t Tuple) AddTuple(t2 Tuple) Tuple {
return Tuple{
x: t.x + t2.x,
y: t.y + t2.y,
z: t.z + t2.z,
w: t.w + t2.w}
}
func (t Tuple) Negate() Tuple {
return Tuple{
x: -t.x,
y: -t.y,
z: -t.z,
w: -t.w}
}
func (t Tuple) Multiply(multiplier float64) Tuple {
return Tuple{
x: t.x * multiplier,
y: t.y * multiplier,
z: t.z * multiplier,
w: t.w * multiplier}
}
func (t Tuple) Divide(divider float64) Tuple {
return Tuple{
x: t.x / divider,
y: t.y / divider,
z: t.z / divider,
w: t.w / divider}
}
func (t Tuple) EqualTo(t2 Tuple) bool {
return round(t.x, 8) == round(t2.x, 8) &&
round(t.y, 8) == round(t2.y, 8) &&
round(t.z, 8) == round(t2.z, 8) &&
round(t.w, 8) == round(t2.w, 8)
}

93
model/tuple_test.go Executable file
View file

@ -0,0 +1,93 @@
package model
import (
"testing"
)
func TestCanCreateATuple(t *testing.T) {
point := Tuple{x: 1, y: 2, z: 3, w: 4}
if point.X() != 1 {
t.Error("Expected for x:", 1, "Got:", point.x)
}
if point.Y() != 2 {
t.Error("Expected for y:", 2, "Got:", point.y)
}
if point.Z() != 3 {
t.Error("Expected for z:", 3, "Got:", point.z)
}
if point.W() != 4 {
t.Error("Expected for w:", 4, "Got:", point.w)
}
}
func TestTupleCanBeAPointOrVector(t *testing.T) {
vector := Tuple{x: 1, y: 2, z: 3, w: 0}
point := Tuple{x: 1, y: 2, z: 3, w: 1}
if vector.IsVector() != true {
t.Error("Expected tuple to be a vector")
}
if point.IsPoint() != true {
t.Error("Expected tuple to be a point")
}
}
func TestCanAddUpTwoTuples(t *testing.T) {
tuple1 := Tuple{x: 3, y: -2, z: 5, w: 1}
tuple2 := Tuple{x: -2, y: 3, z: 1, w: 0}
expected := Tuple{x: 1, y: 1, z: 6, w: 1}
actual := tuple1.AddTuple(tuple2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanNegateATuple(t *testing.T) {
tuple := Tuple{x: 1, y: -2, z: 3, w: -4}
expected := Tuple{x: -1, y: 2, z: -3, w: 4}
actual := tuple.Negate()
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanMultiplyATupleByAScalar(t *testing.T) {
tuple1 := Tuple{x: 1, y: -2, z: 3, w: -4}
scalar := 3.5
expected := Tuple{x: 3.5, y: -7, z: 10.5, w: -14}
actual := tuple1.Multiply(scalar)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanMultiplyATupleByAFraction(t *testing.T) {
tuple := Tuple{x: 1, y: -2, z: 3, w: -4}
fraction := 0.5
expected := Tuple{x: 0.5, y: -1, z: 1.5, w: -2}
actual := tuple.Multiply(fraction)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanDivideATupleByAScalar(t *testing.T) {
tuple := Tuple{x: 1, y: -2, z: 3, w: -4}
scalar := 2.0
expected := Tuple{x: 0.5, y: -1, z: 1.5, w: -2}
actual := tuple.Divide(scalar)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}

58
model/vector.go Executable file
View file

@ -0,0 +1,58 @@
package model
import "math"
type Vector struct {
*Tuple
}
func CreateVector(x float64, y float64, z float64) Vector {
return Vector{&Tuple{x: x, y: y, z: z, w: 0}}
}
func (v Vector) AddVector(v2 Vector) Vector {
return CreateVector(v.x+v2.x, v.y+v2.y, v.z+v2.z)
}
func (v Vector) SubtractVector(v2 Vector) Vector {
return CreateVector(v.x-v2.x, v.y-v2.y, v.z-v2.z)
}
func (v Vector) Negate() Vector {
return CreateVector(-v.x, -v.y, -v.z)
}
func (v Vector) Multiply(multiplier float64) Vector {
return CreateVector(v.x*multiplier, v.y*multiplier, v.z*multiplier)
}
func (v Vector) Divide(divider float64) Vector {
return CreateVector(v.x/divider, v.y/divider, v.z/divider)
}
func (v Vector) Magnitude() float64 {
return math.Sqrt(v.x*v.x + v.y*v.y + v.z*v.z)
}
func (v Vector) Normalize() Vector {
magnitude := v.Magnitude()
return CreateVector(v.x/magnitude, v.y/magnitude, v.z/magnitude)
}
func (v Vector) Dot(v2 Vector) float64 {
return v.x*v2.x + v.y*v2.y + v.z*v2.z + v.w*v2.w
}
func (v Vector) Cross(v2 Vector) Vector {
return CreateVector(v.y*v2.z-v.z*v2.y,
v.z*v2.x-v.x*v2.z,
v.x*v2.y-v.y*v2.x)
}
func (v Vector) EqualTo(v2 Vector) bool {
return round(v.x, 8) == round(v2.x, 8) &&
round(v.y, 8) == round(v2.y, 8) &&
round(v.z, 8) == round(v2.z, 8) &&
round(v.w, 8) == round(v2.w, 8)
}

156
model/vector_test.go Executable file
View file

@ -0,0 +1,156 @@
package model
import (
"math"
"testing"
)
func TestCanCreateAVector(t *testing.T) {
vector := CreateVector(4, -4, 3)
if vector.x != 4 {
t.Error("Expected for x:", 4, "Got:", vector.x)
}
if vector.y != -4 {
t.Error("Expected for y:", -4, "Got:", vector.y)
}
if vector.z != 3 {
t.Error("Expected for z:", 3, "Got:", vector.z)
}
if vector.IsVector() != true {
t.Error("Expected a point")
}
}
func TestCanAddAVectorToAVector(t *testing.T) {
vector1 := CreateVector(3, -2, 1)
vector2 := CreateVector(1, 1, -1)
expected := CreateVector(4, -1, 0)
actual := vector1.AddVector(vector2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanSubtractTwoVectors(t *testing.T) {
vector1 := CreateVector(3, 2, 1)
vector2 := CreateVector(5, 6, 7)
expected := CreateVector(-2, -4, -6)
actual := vector1.SubtractVector(vector2)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanNegateAVector(t *testing.T) {
vector := CreateVector(1, -2, 3)
expected := CreateVector(-1, 2, -3)
actual := vector.Negate()
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanMultiplyAVectorByAFraction(t *testing.T) {
vector := CreateVector(1, -2, 3)
fraction := 0.5
expected := CreateVector(0.5, -1, 1.5)
actual := vector.Multiply(fraction)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanDivideAVectorByAScalar(t *testing.T) {
vector := CreateVector(1, -2, 3)
scalar := 2.0
expected := CreateVector(0.5, -1, 1.5)
actual := vector.Divide(scalar)
if actual.EqualTo(expected) == false {
t.Error("Expected:", expected, "Got:", actual)
}
}
func TestCanComputeTheMagnitude(t *testing.T) {
vector1 := CreateVector(1, 0, 0)
vector2 := CreateVector(0, 1, 0)
vector3 := CreateVector(0, 0, 1)
vector4 := CreateVector(1, 2, 3)
vector5 := CreateVector(-1, -2, -3)
if vector1.Magnitude() != 1 {
t.Error("Expected magnitude of:", 1, "Got:", vector1.Magnitude())
}
if vector2.Magnitude() != 1 {
t.Error("Expected magnitude of:", 1, "Got:", vector2.Magnitude())
}
if vector3.Magnitude() != 1 {
t.Error("Expected magnitude of:", 1, "Got:", vector3.Magnitude())
}
if vector4.Magnitude() != math.Sqrt(14) {
t.Error("Expected magnitude of:", math.Sqrt(14), "Got:", vector4.Magnitude())
}
if vector5.Magnitude() != math.Sqrt(14) {
t.Error("Expected magnitude of:", math.Sqrt(14), "Got:", vector5.Magnitude())
}
}
func TestCanNormalizingAVector(t *testing.T) {
vector1 := CreateVector(4, 0, 0)
vector2 := CreateVector(1, 2, 3)
expected1 := CreateVector(1, 0, 0)
actual1 := vector1.Normalize()
if actual1.EqualTo(expected1) == false {
t.Error("Expected:", expected1, "Got:", actual1)
}
expected2 := CreateVector(1/math.Sqrt(14), 2/math.Sqrt(14), 3/math.Sqrt(14))
actual2 := vector2.Normalize()
if actual2.EqualTo(expected2) == false {
t.Error("Expected:", expected2, "Got:", actual2)
}
if actual2.Magnitude() != 1 {
t.Error("Expected magnitude:", 1, "Got:", actual2.Magnitude())
}
}
func TestCanCalculateDotProduct(t *testing.T) {
vector1 := CreateVector(1, 2, 3)
vector2 := CreateVector(2, 3, 4)
if vector1.Dot(vector2) != 20 {
t.Error("Expected dot product:", 20, "Got:", vector1.Dot(vector2))
}
}
func TestCanCalculateCrossProduct(t *testing.T) {
vector1 := CreateVector(1, 2, 3)
vector2 := CreateVector(2, 3, 4)
expected1 := CreateVector(-1, 2, -1)
actual1 := vector1.Cross(vector2)
if actual1.EqualTo(expected1) == false {
t.Error("Expected:", expected1, "Got:", actual1)
}
expected2 := CreateVector(1, -2, 1)
actual2 := vector2.Cross(vector1)
if actual2.EqualTo(expected2) == false {
t.Error("Expected:", expected2, "Got:", actual2)
}
}