// Handle movement assuming there is a physics body associated with the camera. // This attempts to smooth out movement by adding a higher initial velocity push // and then capping movement once max accelleration is reached. func (c *cam) move(bod vu.Pov, x, y, z float64, dir *lin.Q) { if body := bod.Body(); body != nil { boost := 40.0 // kick into high gear from stop. maxAccel := 10.0 // limit accelleration. sx, _, sz := body.Speed() if x != 0 { switch { case sx == 0.0: // apply push in the current direction. dx, dy, dz := lin.MultSQ(x*boost, 0, 0, dir) body.Push(dx, dy, dz) case math.Abs(sx) < maxAccel && math.Abs(sz) < maxAccel: dx, dy, dz := lin.MultSQ(x, 0, 0, dir) body.Push(dx, dy, dz) } } if z != 0 { switch { case sz == 0.0: dx, dy, dz := lin.MultSQ(0, 0, z*boost, dir) body.Push(dx, dy, dz) case math.Abs(sx) < maxAccel && math.Abs(sz) < maxAccel: dx, dy, dz := lin.MultSQ(0, 0, z, dir) body.Push(dx, dy, dz) } } } else { bod.Move(x, y, z, dir) } }
// newPlayer sets the player hud location and creates the white background. func newPlayer(root vu.Pov, screenWidth, screenHeight int) *player { pl := &player{} pl.cx, pl.cy = 100, 100 pl.bg = root.NewPov().SetScale(110, 110, 1).SetLocation(pl.cx, pl.cy, 0) pl.bg.NewModel("uv").LoadMesh("icon").AddTex("hudbg") return pl }
// newCube's are often started with 1 corner, 2 edges, or 4 bottom side pieces. func newCube(tr vu.Pov, x, y, z, cubeSize float64) *cube { c := &cube{} c.part = tr.NewPov() c.cells = []vu.Pov{} c.cx, c.cy, c.cz, c.csize = x, y, z, cubeSize c.ccnt, c.cmax = 0, 8 c.mergec = func() { c.merge() } c.trashc = func() { c.trash() } c.addc = func() { c.addCell() } c.remc = func() { c.removeCell() } // calculate the cell center locations (unsorted) qs := c.csize * 0.25 c.centers = csort{ &lin.V3{X: x - qs, Y: y - qs, Z: z - qs}, &lin.V3{X: x - qs, Y: y - qs, Z: z + qs}, &lin.V3{X: x - qs, Y: y + qs, Z: z - qs}, &lin.V3{X: x - qs, Y: y + qs, Z: z + qs}, &lin.V3{X: x + qs, Y: y - qs, Z: z - qs}, &lin.V3{X: x + qs, Y: y - qs, Z: z + qs}, &lin.V3{X: x + qs, Y: y + qs, Z: z - qs}, &lin.V3{X: x + qs, Y: y + qs, Z: z + qs}, } return c }
// newMinimap initializes the minimap. It still needs to be populated. func newMinimap(root vu.Pov, numTroops int) *minimap { mm := &minimap{} mm.radius = 120 mm.scale = 5.0 mm.cores = []vu.Pov{} mm.view = root.NewView() mm.view.SetUI() mm.view.SetCull(vu.NewRadiusCull(float64(mm.radius))) mm.cam = mm.view.Cam() mm.cam.SetTransform(vu.XZ_XY) // create the parent for all the visible minimap pieces. mm.part = root.NewPov().SetLocation(float64(mm.x), 0, float64(-mm.y)) // add the white background. mm.bg = mm.part.NewPov().SetScale(110, 1, 110) mm.bg.NewModel("uv").LoadMesh("icon_xz").AddTex("hudbg") // create the sentinel position markers mm.spms = []vu.Pov{} for cnt := 0; cnt < numTroops; cnt++ { tpm := mm.part.NewPov().SetScale(mm.scale, mm.scale, mm.scale) tpm.NewModel("alpha").LoadMesh("square_xz").LoadMat("tred") mm.spms = append(mm.spms, tpm) } // create the player marker and center map marker. mm.cpm = mm.part.NewPov().SetScale(mm.scale, mm.scale, mm.scale) mm.cpm.NewModel("alpha").LoadMesh("square_xz").LoadMat("blue") mm.ppm = mm.part.NewPov().SetScale(mm.scale, mm.scale, mm.scale) mm.ppm.NewModel("alpha").LoadMesh("tri_xz").LoadMat("tblack") return mm }
func (rl *rltag) newText(parent vu.Pov, gap int) vu.Model { text := parent.NewPov().SetLocation(10, 0, float64(-rl.wh+40+gap*24)) text.Spin(-90, 0, 0) // orient to the X-Z plane. m := text.NewModel("uv").AddTex("lucidiaSu16White").LoadFont("lucidiaSu16") m.SetPhrase(" ") return m }
// makePlayer: the player is the camera... the player-trooper is used by the hud // to show player status and as such this trooper is part of the hud scene. func (lvl *level) makePlayer(root vu.Pov, levelNum int) *trooper { player := newTrooper(root.NewPov(), levelNum) player.part.Spin(15, 0, 0) player.part.Spin(0, 15, 0) player.setScale(100) player.part.SetListener() return player }
// createCore makes the new core model. // Create a core image using a single multi-texture shader. func (cc *coreControl) createCore(root vu.Pov, fade float64) vu.Pov { core := root.NewPov().SetScale(0.25, 0.25, 0.25) model := core.NewModel("spinball").LoadMesh("billboard") model.AddTex("ele").AddTex("ele").AddTex("halo").AddTex("halo") model.SetAlpha(0.6) model.SetUniform("fd", fade) return core }
// energyLossEffect creates the model shown when the player gets hit // by a sentinel. func (hd *hud) energyLossEffect(root vu.Pov) vu.Pov { ee := root.NewPov() ee.SetVisible(false) m := ee.NewModel("uvra").LoadMesh("icon").AddTex("loss") m.SetAlpha(0.5) m.SetUniform("fd", 1000) m.SetUniform("spin", 2.0) return ee }
// teleportEffect creates the model shown when the user teleports. func (hd *hud) teleportEffect(root vu.Pov) vu.Pov { te := root.NewPov() te.SetVisible(false) m := te.NewModel("uvra").LoadMesh("icon").AddTex("smoke") m.SetAlpha(0.5) m.SetUniform("spin", 10.0) m.SetUniform("fd", 1000) return te }
// makeSphere creates a sphere at the given x, y, z location and with // the given r, g, b colour. func (rc *rctag) makeSphere(parent vu.Pov, x, y, z float64, r, g, b float32) vu.Pov { sz := 0.5 sp := parent.NewPov() sp.NewBody(vu.NewSphere(sz)) sp.SetLocation(x, y, z) sp.SetScale(sz, sz, sz) model := sp.NewModel("solid").LoadMesh("sphere") model.SetUniform("kd", r, g, b) return sp }
// makeSentries creates some AI sentinels. func (lvl *level) makeSentries(root vu.Pov, levelNum int) { sentinels := []*sentinel{} numSentinels := gameMuster[levelNum] for cnt := 0; cnt < numSentinels; cnt++ { sentry := newSentinel(root.NewPov(), levelNum, lvl.units, lvl.fade) sentry.setScale(0.25) sentinels = append(sentinels, sentry) } lvl.sentries = sentinels }
// newStartAnimation creates the start screen animation. func newStartAnimation(mp *bampf, parent vu.Pov, screenWidth, screenHeight int) *startAnimation { sa := &startAnimation{} sa.parent = parent sa.scale = 200 sa.hilite = parent.NewPov() sa.hilite.NewModel("alpha").LoadMesh("square").LoadMat("white") sa.hilite.SetVisible(false) sa.resize(screenWidth, screenHeight) sa.showLevel(0) return sa }
// newPanel creates a panel with no cubes. The cubes are added later using // panel.addCube(). func newPanel(part vu.Pov, x, y, z float64, level int) *panel { p := &panel{} p.part = part.NewPov() p.lvl = level p.cubes = []*cube{} p.cx, p.cy, p.cz = x, y, z p.ccnt, p.cmax = 0, (level-1)*(level-1)*8 p.mergec = func() { p.merge() } p.trashc = func() { p.trash() } p.addc = func() { p.addCell() } p.remc = func() { p.removeCell() } return p }
// newElectron creates a new electron model. func newElectron(root vu.Pov, band int, angle float64) *electron { ele := &electron{} ele.band = band x, y := ele.initialLocation(angle) ele.core = root.NewPov().SetLocation(x, y, 0) // rotating image. cimg := ele.core.NewPov().SetScale(0.25, 0.25, 0.25) model := cimg.NewModel("spinball").LoadMesh("billboard") model.AddTex("ele").AddTex("ele").AddTex("halo").AddTex("halo") model.SetAlpha(0.6) return ele }
// label adds a banner to a button or updates the banner if there is // an existing banner. func (b *button) label(part vu.Pov, keyCode int) { if keysym := vu.Keysym(keyCode); keysym > 0 { texture := "lucidiaSu22Black" if b.banner == nil { b.banner = part.NewPov().SetLocation(float64(b.x), float64(b.y), 0) b.banner.NewModel("uv").AddTex(texture).LoadFont("lucidiaSu22") } if keyCode == 0 { keyCode = vu.K_Space } b.banner.Model().SetPhrase(string(keysym)) } }
// newBlock creates a panel with no cubes. The cubes are added later using // panel.addCube(). func newBlock(part vu.Pov, x, y, z float64, level int) *block { b := &block{} b.part = part.NewPov() b.lvl = level b.cubes = []*cube{} b.cx, b.cy, b.cz = x, y, z b.ccnt, b.cmax = 0, (level-1)*(level-1)*8 b.mergec = func() { b.merge() } b.trashc = func() { b.trash() } b.addc = func() { b.addCell() } b.remc = func() { b.removeCell() } return b }
// newSentinel creates a player enemy. func newSentinel(part vu.Pov, level, units int, fade float64) *sentinel { s := &sentinel{} s.part = part s.units = float64(units) s.part.SetLocation(0, 0.5, 0) if level > 0 { s.center = s.part.NewPov().SetScale(0.125, 0.125, 0.125) m := s.center.NewModel("flata").LoadMesh("cube").LoadMat("tred") m.SetUniform("fd", fade) } s.model = part.NewPov() m := s.model.NewModel("flata").LoadMesh("cube").LoadMat("tblue") m.SetUniform("fd", fade) return s }
// newButton creates a button. Buttons are initialized with a size and repositioned later. // root is the parent transform. // size is both the width and height. // icon is the (already loaded) texture image. // action is the action to perform when the button is pressed. func newButton(root vu.Pov, size int, icon string, eventId int, eventData interface{}) *button { btn := &button{} btn.model = root.NewPov() btn.eventId = eventId btn.eventData = eventData btn.w, btn.h = size, size // create the button icon. btn.id = icon btn.icon = btn.model.NewPov().SetScale(float64(btn.w/2), float64(btn.h/2), 1) btn.icon.NewModel("uv").LoadMesh("icon").AddTex(icon).SetAlpha(0.5) // create a hilite that is only shown on mouse over. btn.hilite = btn.model.NewPov().SetScale(float64(btn.w/2.0), float64(btn.h/2.0), 1) btn.hilite.SetVisible(false) btn.hilite.NewModel("alpha").LoadMesh("square").LoadMat("tblue") return btn }
// buildFloorPlan creates the level layout. func (lvl *level) buildFloorPlan(root vu.Pov, hd *hud, plan grid.Grid) { width, height := plan.Size() for x := 0; x < width; x++ { for y := 0; y < height; y++ { xc := float64(x * lvl.units) yc := float64(-y * lvl.units) band := plan.Band(x, y) / 3 if x == width/2 && y == height/2 { lvl.gcx, lvl.gcy = x, y // remember the maze center location lvl.center = root.NewPov().SetLocation(xc, 0, yc) m := lvl.center.NewModel("uvra").LoadMesh("tile").AddTex("drop1") m.SetAlpha(0.7) m.SetUniform("spin", 1.0) m.SetUniform("fd", lvl.fade) } else if plan.IsOpen(x, y) { // the floor tiles. tileLabel := lvl.tileLabel(band) tile := root.NewPov().SetLocation(xc, 0, yc) m := tile.NewModel("uva").LoadMesh("tile").AddTex(tileLabel) m.SetAlpha(0.7) m.SetUniform("fd", lvl.fade) // remember the tile locations for drop spots inside the maze. lvl.cc.addDropLocation(x, y) } else { // draw flat on the y plane with the maze extending into the screen. wm := lvl.wallMeshLabel(band) wt := lvl.wallTextureLabel(band) wall := root.NewPov().SetLocation(xc, 0, yc) m := wall.NewModel("uva").LoadMesh(wm).AddTex(wt) m.SetUniform("fd", lvl.fade) lvl.walls = append(lvl.walls, wall) // add the wall to the minimap hd.addWall(xc, yc) } } } // add core drop locations around the outside of the maze. for x := -1; x < width+1; x++ { lvl.cc.addDropLocation(x, -1) lvl.cc.addDropLocation(x, height) } for y := 0; y < height; y++ { lvl.cc.addDropLocation(-1, y) lvl.cc.addDropLocation(width, y) } }
// cloakingEffect creates the model shown when the user cloaks. func (hd *hud) cloakingEffect(root vu.Pov) vu.Pov { ce := root.NewPov() ce.SetVisible(false) ce.NewModel("uv").LoadMesh("icon").AddTex("cloakon").SetAlpha(0.5) return ce }
// getBall creates a visible sphere physics body. func (cr *crtag) getBall(p vu.Pov) { p.NewBody(vu.NewSphere(1)) p.SetSolid(1, 0.5) p.NewModel("gouraud").LoadMesh("sphere").LoadMat("sphere") }
// newXpbar creates all three status bars. func newXpbar(root vu.Pov, screenWidth, screenHeight int) *xpbar { xp := &xpbar{} xp.border = 5 xp.linew = 2 xp.setSize(screenWidth, screenHeight) // add the xp background and foreground bars. xp.bg = root.NewPov() xp.bg.NewModel("alpha").LoadMesh("square").LoadMat("tblack") xp.fg = root.NewPov() xp.fg.NewModel("uv").LoadMesh("icon").AddTex("xpgreen") // add the xp bar text. xp.hb = root.NewPov() m := xp.hb.NewModel("uv").AddTex("lucidiaSu22White").LoadFont("lucidiaSu22") xp.hbw = m.SetPhrase("0").PhraseWidth() // teleport energy background and foreground bars. xp.tbg = root.NewPov() xp.tbg.NewModel("alpha").LoadMesh("square").LoadMat("tblack") xp.tfg = root.NewPov() xp.tfg.NewModel("uv").LoadMesh("icon").AddTex("xpblue") // the teleport bar text. xp.tk = root.NewPov() m = xp.tk.NewModel("uv").AddTex("lucidiaSu18White").LoadFont("lucidiaSu18") xp.tkw = m.SetPhrase("0").PhraseWidth() // cloak energy background and foreground bars. xp.cbg = root.NewPov() xp.cbg.NewModel("alpha").LoadMesh("square").LoadMat("tblack") xp.cfg = root.NewPov() xp.cfg.NewModel("uv").LoadMesh("icon").AddTex("xpblue") // the cloak bar text. xp.ck = root.NewPov() m = xp.ck.NewModel("uv").AddTex("lucidiaSu18White").LoadFont("lucidiaSu18") xp.ckw = m.SetPhrase("0").PhraseWidth() xp.resize(screenWidth, screenHeight) return xp }
// getBox creates a visible box physics body. func (cr *crtag) getBox(p vu.Pov) { p.SetScale(2, 2, 2) p.NewBody(vu.NewBox(1, 1, 1)) p.SetSolid(1, 0) p.NewModel("gouraud").LoadMesh("cube").LoadMat("sphere") }
// newTrooper creates a trooper for the given level. // level 0: 1x1x1 : 0 edge cubes 0 panels, (only 1 cube) // level 1: 2x2x2 : 8 edge cubes + 6 panels of 0x0 cubes + 0x0x0 center. // level 2: 3x3x3 : 20 edge cubes + 6 panels of 1x1 cubes + 1x1x1 center. // level 3: 4x4x4 : 32 edge cubes + 6 panels of 2x2 cubes + 2x2x2 center. // ... func newTrooper(part vu.Pov, level int) *trooper { tr := &trooper{} tr.lvl = level tr.part = part tr.bits = []box{} tr.ipos = []int{} tr.mid = tr.lvl*tr.lvl*tr.lvl*8 - (tr.lvl-1)*(tr.lvl-1)*(tr.lvl-1)*8 // set max energies. tr.cemax, tr.temax = 1000, 1000 // special case for a level 0 (start screen) trooper. if tr.lvl == 0 { cube := newCube(tr.part, 0, 0, 0, 1) cube.edgeSort(1) tr.bits = append(tr.bits, cube) return tr } // create the panels. These are used in each level after level 1. cubeSize := 1.0 / float64(tr.lvl+1) centerOffset := cubeSize * 0.5 panelCenter := float64(tr.lvl) * centerOffset tr.bits = append(tr.bits, newPanel(tr.part, panelCenter, 0.0, 0.0, tr.lvl)) tr.bits = append(tr.bits, newPanel(tr.part, -panelCenter, 0.0, 0.0, tr.lvl)) tr.bits = append(tr.bits, newPanel(tr.part, 0.0, panelCenter, 0.0, tr.lvl)) tr.bits = append(tr.bits, newPanel(tr.part, 0.0, -panelCenter, 0.0, tr.lvl)) tr.bits = append(tr.bits, newPanel(tr.part, 0.0, 0.0, panelCenter, tr.lvl)) tr.bits = append(tr.bits, newPanel(tr.part, 0.0, 0.0, -panelCenter, tr.lvl)) // troopers are made out of cubes and panels. mx := float64(-tr.lvl) for cx := 0; cx <= tr.lvl; cx++ { my := float64(-tr.lvl) for cy := 0; cy <= tr.lvl; cy++ { mz := float64(-tr.lvl) for cz := 0; cz <= tr.lvl; cz++ { // create the outer edges. newCells := 0 if (cx == 0 || cx == tr.lvl) && (cy == 0 || cy == tr.lvl) && (cz == 0 || cz == tr.lvl) { // corner cube newCells = 1 } else if (cx == 0 || cx == tr.lvl) && (cy == 0 || cy == tr.lvl) || (cx == 0 || cx == tr.lvl) && (cz == 0 || cz == tr.lvl) || (cy == 0 || cy == tr.lvl) && (cz == 0 || cz == tr.lvl) { // edge cube newCells = 2 } else if cx == 0 || cx == tr.lvl || cy == 0 || cy == tr.lvl || cz == 0 || cz == tr.lvl { // side cubes are added to a panel. x, y, z := mx*centerOffset, my*centerOffset, mz*centerOffset if cx == tr.lvl && x > y && x > z { tr.bits[0].(*panel).addCube(x, y, z, float64(cubeSize)) } else if cx == 0 && x < y && x < z { tr.bits[1].(*panel).addCube(x, y, z, float64(cubeSize)) } else if cy == tr.lvl && y > x && y > z { tr.bits[2].(*panel).addCube(x, y, z, float64(cubeSize)) } else if cy == 0 && y < x && y < z { tr.bits[3].(*panel).addCube(x, y, z, float64(cubeSize)) } else if cz == tr.lvl && z > x && z > y { tr.bits[4].(*panel).addCube(x, y, z, float64(cubeSize)) } else if cz == 0 && z < x && z < y { tr.bits[5].(*panel).addCube(x, y, z, float64(cubeSize)) } } if newCells > 0 { x, y, z := mx*centerOffset, my*centerOffset, mz*centerOffset cube := newCube(tr.part, x, y, z, float64(cubeSize)) cube.edgeSort(newCells) tr.bits = append(tr.bits, cube) } mz += 2 } my += 2 } mx += 2 } tr.addCenter() // its easier to remember the initial positions than recalculate them. tr.ipos = make([]int, len(tr.bits)) for cnt, b := range tr.bits { tr.ipos[cnt] = b.box().ccnt } // create the noises the trooper can make. tr.noise = part.NewNoise() tr.noise.Add("teleport") // teleportSound tr.noise.Add("fetch") // fetchSound tr.noise.Add("cloak") // cloakSound tr.noise.Add("decloak") // decloakSound tr.noise.Add("collide") // collideSound return tr }
// chaser moves towards a goal. func newChaser(parent vu.Pov) *chaser { c := &chaser{} c.pov = parent.NewPov() c.pov.NewModel("uv").LoadMesh("icon").AddTex("token") return c }