Пример #1
0
func newBody(shape Shape) *body {
	b := &body{}
	b.shape = shape
	b.imass = 0                 // no mass, static body by default
	b.friction = 0.5            // good to have some friction
	b.world = lin.NewT().SetI() // world transform
	b.guess = lin.NewT().SetI() // predicted world transform

	// allocate linear and angular motion data
	b.lvel = lin.NewV3()
	b.lfor = lin.NewV3()
	b.avel = lin.NewV3()
	b.afor = lin.NewV3()
	b.iitw = lin.NewM3().Set(lin.M3I)
	b.iit = lin.NewV3()

	// allocate scratch variables
	b.coi = &C.BoxBoxInput{}
	b.cor = &C.BoxBoxResults{}
	b.m0 = &lin.M3{}
	b.m1 = &lin.M3{}
	b.v0 = &lin.V3{}
	b.t0 = lin.NewT()

	// create a unique body identifier
	bodyUuidMutex.Lock()
	b.bid = bodyUuid
	if bodyUuid++; bodyUuid == 0 {
		log.Printf("Overflow: dev error. Unique body id wrapped.")
	}
	bodyUuidMutex.Unlock()
	return b
}
Пример #2
0
func TestLargestArea(t *testing.T) {
	con := &contactPair{}
	con.v0, con.v1, con.v2 = lin.NewV3(), lin.NewV3(), lin.NewV3()

	// Existing points: essentially 14,0,+-1, 16,0,+-1
	manifold := newManifold()
	manifold[0].sp.localA.SetS(13.993946, 25.000000, -0.999210) // 14,0,-1
	manifold[1].sp.localA.SetS(14.006243, 25.000000, 0.979937)  // 14,0,1
	manifold[2].sp.localA.SetS(15.989870, 25.000000, 0.996212)  // 16,0,1
	manifold[3].sp.localA.SetS(15.993749, 25.000000, -0.999743) // 16,0,-1

	// new point A should replace existing point 0.
	ptA := newPoc()
	ptA.sp.localA.SetS(14.024626, 25.000000, -1.020002) // 14,0,-1
	if index := con.largestArea(manifold, ptA); index != 0 {
		t.Errorf("Wrong replacement ptA for best contact area %d", index)
	}

	// new point A should replace existing point 1.
	ptB := newPoc()
	ptB.sp.localA.SetS(14.008444, 25.000000, 0.979925) // 14,0,1
	if index := con.largestArea(manifold, ptB); index != 1 {
		t.Errorf("Wrong replacement ptB for best contact area %d", index)
	}
}
Пример #3
0
// newPoc allocates space for, and returns, a pointOfContact structure.
func newPoc() *pointOfContact {
	poc := &pointOfContact{}
	poc.point = lin.NewV3()
	poc.normal = lin.NewV3()
	poc.sp = newSolverPoint()
	poc.v0 = lin.NewV3()
	return poc
}
Пример #4
0
// sample calculates the colour value for a given ray (origin and direction)
// shot into the scene. It relies on the trace() method to determine what the
// ray hit and recursively calls itself to add in the colour values of any
// child rays. This method performs the job of a rasterization pipeline shader
// by calculating colour based on light and normals wherever rays hit scene
// objects.
func (rt *rtrace) sample(orig, dir lin.V3, seed *uint32) (colour lin.V3) {
	rt.sampleCalls += 1                     // track number of times called
	st, dist, bounce := rt.trace(orig, dir) // check ray scene collision.
	obounce := bounce

	// generate a sky colour if the ray is going up.
	if st == missHigh {
		p := 1 - dir.Z // make the sky colour lighter closer to the horizon.
		p = p * p
		p = p * p
		colour.SetS(0.7, 0.6, 1)
		return *colour.Scale(&colour, p)
	}

	// add randomness to light for soft shadows.
	hitAt, lightDir, tmpv := lin.NewV3(), lin.NewV3(), lin.NewV3()
	hitAt.Add(&orig, tmpv.Scale(&dir, dist))
	lightDir.SetS(9+rnd(seed), 9+rnd(seed), 16).Add(lightDir, tmpv.Scale(hitAt, -1)).Unit()
	lightIntensity := lightDir.Dot(&bounce) // lambertian factor based on angle of light source.

	// check if the spot is in shadow by tracing a ray from the
	// intersection point to the light. Its in shadow if there is a hit.
	shadowFactor := 1.0
	if lightIntensity < 0 {
		lightIntensity = 0
		shadowFactor = 0
	} else {
		var hitStatus int
		if hitStatus, dist, bounce = rt.trace(*hitAt, *lightDir); hitStatus != missHigh {
			lightIntensity = 0
			shadowFactor = 0
		}
	}

	// generate a floor colour if the ray was going down.
	if st == missLow {
		hitAt.Scale(hitAt, 0.2)
		colour.SetS(3, 3, 3) // gray floor squares.
		if int(math.Ceil(hitAt.X)+math.Ceil(hitAt.Y))&1 == 1 {
			colour.SetS(1, 1, 3) // blue floor squares.
		}
		return *(colour.Scale(&colour, lightIntensity*0.2+0.1))
	}

	// calculate the colour 'rgb' with diffuse and specular component.
	// r is the reflection vector.
	reflectDir := lin.NewV3()
	reflectDir.Add(&dir, tmpv.Scale(&obounce, obounce.Dot(tmpv.Scale(&dir, -2))))
	rgb := lightDir.Dot(reflectDir.Scale(reflectDir, shadowFactor))
	rgb = math.Pow(rgb, 99)

	// cast a child ray from where the parent ray hit.
	// Add in the result of the colour from the child ray.
	colour.SetS(rgb, rgb, rgb)
	addColour := rt.sample(*hitAt, *reflectDir, seed)
	addColour.Scale(&addColour, 0.5)
	return *(colour.Add(&colour, &addColour))
}
Пример #5
0
// newSolverConstraint allocates the memory needed for a solver constraint.
func newSolverConstraint() *solverConstraint {
	sc := &solverConstraint{}
	sc.normal = lin.NewV3()
	sc.relpos1CrossNormal = lin.NewV3()
	sc.relpos2CrossNormal = lin.NewV3()
	sc.angularComponentA = lin.NewV3()
	sc.angularComponentB = lin.NewV3()
	return sc
}
Пример #6
0
// newSolver creates the necessary space for the solver to work.
// This is expected to be called once on engine startup.
func newSolver() *solver {
	sol := &solver{}
	sol.info = newSolverInfo()
	sol.constC = []*solverConstraint{}
	sol.constF = []*solverConstraint{}
	sol.v0 = lin.NewV3()
	sol.v1 = lin.NewV3()
	sol.v2 = lin.NewV3()
	sol.ra = lin.NewV3()
	sol.rb = lin.NewV3()
	return sol
}
Пример #7
0
// newContactPair creates a contact between two bodies. This is expected to be
// used only for contacting bodies that are not already being tracked by the
// mover.
func newContactPair(bodyA, bodyB *body) *contactPair {
	con := &contactPair{}
	con.bodyA, con.bodyB = bodyA, bodyB
	if bodyA != nil && bodyB != nil {
		con.pid = bodyA.pairId(bodyB)
	}
	con.pocs = newManifold() // allocate space for 4 contacts.
	con.pocs = con.pocs[0:0] // start with zero contacts.
	con.breakingLimit = 0.02
	con.processingLimit = lin.LARGE
	con.v0 = lin.NewV3()
	con.v1 = lin.NewV3()
	con.v2 = lin.NewV3()
	return con
}
Пример #8
0
func TestGetVelocityInLocalPoint(t *testing.T) {
	b := newBody(NewSphere(1)).SetMaterial(0.5, 0.8).(*body)
	b.lvel.SetS(2, 2, 2)
	b.avel.SetS(3, 3, 3)
	v, p, want := lin.NewV3(), lin.NewV3S(1, 1, 1), "{2.0 2.0 2.0}"
	if b.getVelocityInLocalPoint(p, v); dumpV3(v) != want {
		t.Errorf("Expecting local velocity %s, got %s", dumpV3(v), want)
	}
}
Пример #9
0
// render one row of pixels by calculating a colour for each pixel.
// The image pixel row number is r. Fill the pixel colour into the
// image after the colour has been calculated.
func (r row) render(rt *rtrace, a, b, c lin.V3, img *image.NRGBA, seed *uint32) {
	rgba := color.NRGBA{0, 0, 0, 255}
	t, v1, v2 := lin.NewV3(), lin.NewV3(), lin.NewV3() // temp vectors.
	colour, orig, dir := lin.NewV3(), lin.NewV3(), lin.NewV3()
	for x := (rt.iw - 1); x >= 0; x-- {
		colour.SetS(13, 13, 13) // Use a very dark default colour.

		// Cast 64 rays per pixel for blur (stochastic sampling) and soft-shadows.
		for cnt := 0; cnt < 64; cnt++ {

			// Add randomness to the camera origin 17,16,8
			t.Scale(&a, rnd(seed)-0.5).Scale(t, 99).Add(t, v1.Scale(&b, rnd(seed)-0.5).Scale(v1, 99))
			orig.SetS(17, 16, 8).Add(orig, t)

			// Add randomness to the camera direction.
			rnda := rnd(seed) + float64(x)
			rndb := float64(r) + rnd(seed)
			dir.Scale(t, -1)
			dir.Add(dir, v1.Scale(&a, rnda).Add(v1, v2.Scale(&b, rndb)).Add(v1, &c).Scale(v1, 16))
			dir.Unit()

			// accumulate the colour from each of the 64 rays.
			sample := rt.sample(*orig, *dir, seed)
			colour = sample.Scale(&sample, 3.5).Add(&sample, colour)
		}

		// set the final pixel colour in the image.
		rgba.R = byte(colour.X) // red
		rgba.G = byte(colour.Y) // green
		rgba.B = byte(colour.Z) // blue
		img.SetNRGBA(rt.iw-x, int(r), rgba)
	}
}
Пример #10
0
// createBaseFrames constructs the joint transform base-pose matricies.
// These are temporary structures used later in genFrame to prepare the
// per-frame animation data.
func (l *loader) createBaseFrames(iqd *IqData, poses []*transform, scr *scratch) {
	i3 := lin.NewM3()                                   // scratch
	vx, vy, vz := lin.NewV3(), lin.NewV3(), lin.NewV3() // scratch
	numJoints := len(poses)
	scr.baseframe = make([]*lin.M4, numJoints)        // joint transforms.
	scr.inversebaseframe = make([]*lin.M4, numJoints) // inverse transforms.
	for cnt := 0; cnt < numJoints; cnt++ {
		j := poses[cnt]

		// Get the joint transform.
		j.q.Unit() // ensure unit quaternion.
		m4 := lin.NewM4().SetQ(j.q)
		m4.Transpose(m4).ScaleSM(j.s.X, j.s.Y, j.s.Z)       // apply scale before rotation.
		m4.Wx, m4.Wy, m4.Wz, m4.Ww = j.t.X, j.t.Y, j.t.Z, 1 // translation added in, not multiplied.
		scr.baseframe[cnt] = m4

		// invert the joint transform for frame generation later on.
		i3.Inv(i3.SetM4(m4))
		itx := -vx.SetS(i3.Xx, i3.Yx, i3.Zx).Dot(j.t)
		ity := -vy.SetS(i3.Xy, i3.Yy, i3.Zy).Dot(j.t)
		itz := -vz.SetS(i3.Xz, i3.Yz, i3.Zz).Dot(j.t)
		i4 := lin.NewM4()
		i4.Xx, i4.Xy, i4.Xz, i4.Xw = i3.Xx, i3.Xy, i3.Xz, 0
		i4.Yx, i4.Yy, i4.Yz, i4.Yw = i3.Yx, i3.Yy, i3.Yz, 0
		i4.Zx, i4.Zy, i4.Zz, i4.Zw = i3.Zx, i3.Zy, i3.Zz, 0
		i4.Wx, i4.Wy, i4.Wz, i4.Ww = itx, ity, itz, 1
		scr.inversebaseframe[cnt] = i4

		// Combine the joint transforms and inverse transform with the parent transform.
		parent := iqd.Joints[cnt]
		if parent >= 0 {
			// childBasePose * parentBasePose
			scr.baseframe[cnt].Mult(scr.baseframe[cnt], scr.baseframe[parent])

			// childInverseBasePose * parentInverseBasePose
			scr.inversebaseframe[cnt].Mult(scr.inversebaseframe[parent], scr.inversebaseframe[cnt])
		}
	}
}
Пример #11
0
// newSolverBody allocates space for body specific solver information.
// This is expected to be called for a movable body, ie. one that has mass
// and can have velocity.
func newSolverBody(bod *body) *solverBody {
	sb := &solverBody{}
	sb.oBody = bod // reference
	sb.world = lin.NewT().Set(bod.world)
	sb.linearVelocity = lin.NewV3().Set(bod.lvel)
	sb.angularVelocity = lin.NewV3().Set(bod.avel)
	sb.deltaLinearVelocity = lin.NewV3()
	sb.deltaAngularVelocity = lin.NewV3()
	sb.pushVelocity = lin.NewV3()
	sb.turnVelocity = lin.NewV3()
	sb.invMass = lin.NewV3().SetS(bod.imass, bod.imass, bod.imass)
	sb.t0 = lin.NewT()
	sb.v0 = lin.NewV3()
	return sb
}
Пример #12
0
// rayTrace creates a single ray traced image.
// Its job is to create the workers that will render each row of the image.
// createScene() needs to have been called first.
func (rt *rtrace) rayTrace() *image.NRGBA {
	start := time.Now()         // track total raytrace time.
	rt.procs = runtime.NumCPU() // equals GOMAXPROCS since Go 1.5.
	img := image.NewNRGBA(image.Rect(0, 0, rt.iw, rt.ih))

	// we're nominally tracing rays for pixel (x,y) in the direction of ax+by+c.
	// At the image midpoint, this should be `g`
	g, a, b, c := lin.NewV3(), lin.NewV3(), lin.NewV3(), lin.NewV3()
	g.SetS(-5.5, -16, 0).Unit()                        // camera direction.
	a.SetS(0, 0, 1).Cross(a, g).Unit().Scale(a, 0.002) // camera up vector.
	b.Cross(g, a).Unit().Scale(b, 0.002)               // right vector.

	// Comment from Aeg:
	// "offset from the eye point (ignoring lens perturbation `t`)
	//  to the corner of the focal plane."
	c.Add(a, b).Scale(c, -256).Add(c, g)

	// create one worker goroutine per processor.
	rows := make(chan row, rt.ih)
	var wg sync.WaitGroup
	wg.Add(rt.procs)
	for i := 0; i < rt.procs; i++ {
		go rt.worker(*a, *b, *c, img, rows, &wg) // pass vectors by value.
	}

	// start assigning image rows to the workers.
	for y := (rt.ih - 1); y >= 0; y-- {
		rows <- row(y)
	}
	close(rows) // closing the worker comm channel causes workers to terminate...
	wg.Wait()   // ... once they finish their current assignment.

	// dump some render statistics.
	used := time.Since(start)
	log.Printf("Sample:%d Trace:%d Time:%fs ", rt.sampleCalls, rt.traceCalls, used.Seconds())
	return img
}
Пример #13
0
// fixedSolverBody lazy initializes and returns the single fixed
// solver body that is used by all static solver bodies.
func fixedSolverBody() *solverBody {
	if fsb == nil {
		fsb = &solverBody{}
		fsb.oBody = nil
		fsb.world = lin.NewT().SetI()
		fsb.linearVelocity = lin.NewV3()
		fsb.angularVelocity = lin.NewV3()
		fsb.deltaLinearVelocity = lin.NewV3()
		fsb.deltaAngularVelocity = lin.NewV3()
		fsb.pushVelocity = lin.NewV3()
		fsb.turnVelocity = lin.NewV3()
		fsb.invMass = lin.NewV3()
		fsb.t0 = lin.NewT()
		fsb.v0 = lin.NewV3()
	}
	return fsb
}
Пример #14
0
// trace casts a ray (origin, direction) to see if the ray hits any
// of the spheres in the scene. The possible return values are:
//   missHigh : no hit and ray goes up.
//   missLow  : no hit and ray goes down.
//   hit      : hit so return distance and reflection ray.
func (rt *rtrace) trace(orig, dir lin.V3) (hitStatus int, minHitDistance float64, bounce lin.V3) {
	rt.traceCalls += 1 // track number of times called.
	minHitDistance = 1e9
	hitStatus = missHigh
	s := -orig.Z / dir.Z
	if 0.01 < s {
		minHitDistance = s
		bounce.SetS(0, 0, 1)
		hitStatus = missLow
	}

	// cast the ray against each sphere in the scene.
	// http://www.lighthouse3d.com/tutorials/maths/ray-sphere-intersection/
	// http://kylehalladay.com/blog/tutorial/math/2013/12/24/Ray-Sphere-Intersection.html
	tempv := lin.NewV3()
	for i, _ := range rt.scene {
		tempv.Add(&orig, &(rt.scene[i])) // ray origin + sphere center.
		b := tempv.Dot(&dir)             // represent the intersection ray...
		c := tempv.Dot(tempv) - 1        // ... and the sphere radius
		b2 := b * b                      // ... using squared magnitudes.

		// if the ray intersected the sphere.
		if b2 > c {
			q := b2 - c                      // convert the squared length values....
			hitDistance := -b - math.Sqrt(q) // ... to the actual collision distance.

			// remember the minimum hit distance.
			if hitDistance < minHitDistance && hitDistance > 0.01 {
				minHitDistance = hitDistance
				bounce = *tempv
				hitStatus = hit
			}
		}
	}
	if hitStatus == hit {
		bounce.Add(&bounce, tempv.Scale(&dir, minHitDistance)).Unit()
	}
	return
}
Пример #15
0
func TestSphereInertia(t *testing.T) {
	sp, inertia, want := Shape(NewSphere(1.25)), lin.NewV3(), "{0.6 0.6 0.6}"
	if sp.Inertia(1, inertia); dumpV3(inertia) != want {
		t.Errorf("Expected sphere inertia %s, got %s", want, dumpV3(inertia))
	}
}
Пример #16
0
func TestBoxInertia(t *testing.T) {
	bx, inertia, want := Shape(NewBox(1, 1, 1)), lin.NewV3(), "{0.7 0.7 0.7}"
	if bx.Inertia(1, inertia); dumpV3(inertia) != want {
		t.Errorf("Expected box inertia %s, got %s", want, dumpV3(inertia))
	}
}