/// <summary> /// Orients the bullet so that it has the same attitude as the shooter, and sets the Tangent of the bullet. /// The Target and WrapFlag of the bullet is set to that of the shooter. The bullet is also set to its /// starting position. /// </summary> /// <param name="pshooter">The shooter of this bullet.</param> public virtual void initialize(cCritterArmed pshooter) { _pshooter = pshooter; setMoveBox(_pshooter.OwnerBiota.border()); /* Be sure to call setMoveBox before setVelocity, * as setVelocity generates a call to fixNormalAndBinormal which looks at _movebox to see * if it's ok if you happen to have a 3D velocity. */ DragBox = _pshooter.OwnerBiota.border(); Attitude = _pshooter.Attitude; //Orient the bullet like the shooter, fix position below. Tangent = _pshooter.AimVector; /* This call uses the _bulletspeed set by the * cBullet constructor and the direction of the aim. We choose NOT to add * _pshooter->velocity() to the new velocity. */ setTarget(_pshooter.Target); WrapFlag = _pshooter.WrapFlag; cVector3 start = _pshooter.Position; //position() /* I want to start the bullet out at the tip of the gun, with the provision * that in any case I will I start it out far enough so that it's not touching * the shooter. */ float bulletdistance1 = _pshooter.GunLength * _pshooter.Radius; float bulletdistance2 = pshooter.Radius + 1.5f * BULLETRADIUS; /* Need the 1.5 for enough * room. Otherwise when the simulation is slow you may still touch. */ float bulletdistance = (bulletdistance1 > bulletdistance2) ? bulletdistance1 : bulletdistance2; cVector3 end = start.add(_pshooter.AimVector.mult(bulletdistance)); moveTo(end); //Do this instead of just setting _position so as to fix wrapposition }
public void setStarPolygon(int vertexcount, float dentpercent, cVector3 center, float newradius = 1.0f, float initangle = 0.0f) { _vectorvert.RemoveAll(); float angle = initangle; float anglestep = (float)Math.PI / vertexcount; bool tipvertex = true; float innerradius = newradius * dentpercent; float currentradius = newradius; for (int i = 0; i < 2 * vertexcount; i++) { if (tipvertex) { currentradius = newradius; } else { currentradius = innerradius; } _vectorvert.Add(center.add( (new cVector3((float)Math.Cos(angle), (float)Math.Sin(angle))).mult(currentradius))); angle += anglestep; tipvertex = !tipvertex; } Center = center; Radius = newradius; _newgeometryflag = true; }
public override void draw(cGraphics pgraphics, int drawflags = 0) { if (_armed) /* The gun looks bad if * you're near the edge in 2D graphics as it doesn't get clipped. */ { /* We draw the "gun" as a line. It might be better to have the following * code be part of the cSprite.drawing, because then that would happen while the * pDC is clipped in the cCritter call. One way to do it would be to have a CpopView.DF_GUN * drawflag. */ cVector3 start = _position; cVector3 end = start.add(_aimvector.mult(_gunlength * Radius)); } base.draw(pgraphics, drawflags); //Draw sprite on top of gun line. }
public void setRegularPolygon(int vertexcount, cVector3 center, float newradius = 1.0f, float initangle = 0.0f) { float angle = initangle; _vectorvert.RemoveAll(); for (int i = 0; i < vertexcount; i++) { _vectorvert.Add(center.add( (new cVector3((float)Math.Cos(angle), (float)Math.Sin(angle))).mult(newradius))); angle += (2 * (float)Math.PI) / vertexcount; } _convex = true; Center = center; _radius = newradius; Radius = _radius; _newgeometryflag = true; }
//Mutators public void setEndsThicknessHeight(cVector3 enda, cVector3 endb, float thickness = THICKNESS, float height = WALLPRISMDZ) { _position = enda.add(endb).mult(0.5f); _wrapposition1.copy(_position); _wrapposition2.copy(_position); _wrapposition3.copy(_position); /* This line is important, as otherwise the cCritter.draw will thing this thing was wrapped, and it'll get drawn in two places. */ _tangent = endb.sub(enda); float length = _tangent.Magnitude; _tangent.normalize(); _oldtangent.copy(_tangent); _normal = _tangent.defaultNormal(); /* We orient so that the normal is oriented to the tangent as the "y-axis" is to the the "x-axis".*/ _binormal = _tangent.mult(_normal); _attitude = new cMatrix3(_tangent, _normal, _binormal, _position); Skeleton = new cRealBox3(length, thickness, height); Speed = 0.0f; /* Also sets _velocity to ZEROVECTOR, but doesn't wipe out _direction. */ /*In looking at these settings, think of the wall as aligned horizontally with endb - enda pointing to the right and the normal pointing into the screen*/ cPolygon ppolygon = new cPolygon(4); ppolygon.Edged = true; ppolygon.FillColor = Color.Gray; ppolygon.LineWidthWeight = cColorStyle.LW_IGNORELINEWIDTHWEIGHT; ppolygon.LineWidth = 1; //Means draw a one-pixel edge line. ppolygon.setVertex(0, new cVector3(0.5f * length, 0.5f * thickness)); ppolygon.setVertex(1, new cVector3(-0.5f * length, 0.5f * thickness)); ppolygon.setVertex(2, new cVector3(-0.5f * length, -0.5f * thickness)); ppolygon.setVertex(3, new cVector3(0.5f * length, -0.5f * thickness)); ppolygon.fixCenterAndRadius(); /* Use this call after a bunch of setVertex if points are just where you want. */ ppolygon.SpriteAttitude = cMatrix3.translation(new cVector3(0.0f, 0.0f, -height / 2.0f)); /* This corrects for the fact that we always draw the ppolygon with its bottom face in the xy plane and its top in the plane z = height. We shift it down so it's drawn to match the skeleton positon. */ Sprite = ppolygon; /* Also sets cSprite._prismdz to cCritter._defaultprismdz, which we set to CritterWall.WALLPRISMDZ in our cCritterWall constructor. */ }
/* Overload this so as not to change velocity as I normally want my walls to be stable and not drift after being dragged. */ public override bool collide(cCritter pcritter) { cVector3 oldlocalpos, newlocalpos; float newdistance; int oldoutcode, newoutcode; bool crossedwall; oldlocalpos = globalToLocalPosition(pcritter.OldPosition); oldoutcode = _pskeleton.outcode(oldlocalpos); newlocalpos = globalToLocalPosition(pcritter.Position); newdistance = _pskeleton.distanceToOutcode(newlocalpos, out newoutcode); //Sets the newoutcode as well. crossedwall = crossed(oldoutcode, newoutcode); if (newdistance >= pcritter.Radius && !crossedwall) //No collision return false; /*See if there's a collision at all. We say there's a collision if crossedwall or if the cCritterWall.distance is less than radius. Remember that cCritterWall.distance measures the distance to the OUTSIDE PERIMETER of the box, not the distance to the box's center. */ /* I collided, so I need to move back further into the last good zone I was in outside the wall. I want to set newlocalpos so the rim of its critter is touching the wall. The idea is to back up in the direction of oldlocalpos. To allow the possibility of skidding along the wall, we plan to back up from the the face (or edge or corner) facing oldlocalpos. This works only if oldlocalpos was a good one, not inside the box. In principle this should always be true, but some rare weird circumstance (like a triple collsion) might mess this up, so we check for the bad case before starting. */ if (oldoutcode == cRealBox3.BOX_INSIDE) //Note that this almost never happens. { cVector3 insidepos = new cVector3(); insidepos.copy(oldlocalpos); oldlocalpos.subassign(pcritter.Tangent.mult(_pskeleton.MaxSize)); //Do a brutally large backup to get out of the box for sure. oldoutcode = _pskeleton.outcode(oldlocalpos); //Recalculate outcode at this new position. oldlocalpos = _pskeleton.closestSurfacePoint(oldlocalpos, oldoutcode, insidepos, cRealBox3.BOX_INSIDE, false); //Go to the closest surface point from there. oldoutcode = _pskeleton.outcode(oldlocalpos); //Recalculate outcode one more time to be safe. crossedwall = crossed(oldoutcode, newoutcode); //Recalculate crossedwall } /* I find that with this code, the mouse can drag things through walls, so I do a kludge to block it by setting crossedwall to TRUE, this affects the action of cRealBox.closestSurfacePoint, as modified in build 34_4. */ if (pcritter.Listener.IsKindOf("cListenerCursor")) crossedwall = true; //Don't trust the mouse listener. newlocalpos = _pskeleton.closestSurfacePoint(oldlocalpos, oldoutcode, newlocalpos, newoutcode, crossedwall); /* This call to closestSurfacePoint will move the newlocal pos from the far new side (or inside, or overlapping) of the box back to the surface, usually on the old near side, edge, or corner given by oldoutcode. This prevents going through the wall. If oldoutcode is a corner position and you are in fact heading towards a face near the corner, we used to bounce off the corner even though visually you can see you should bounce off the face. This had the effect of making a scooter player get hung up on a corner sometimes. As of build 34_3, I'm moving the newlocalpos to the newoutocode side in the case where oldlocalpos is an edge or a corner, and where crossedwall isn't TRUE. I have to force in a TRUE for the cCursorLIstener case. The USEJIGGLE code below also helps keep non-player critters from getting stuck on corners. */ //Now back away from the box. newoutcode = _pskeleton.outcode(newlocalpos); cVector3 avoidbox = _pskeleton.escapeVector(newlocalpos, newoutcode); newlocalpos.addassign(avoidbox.mult(pcritter.Radius)); newoutcode = _pskeleton.outcode(newlocalpos); pcritter.moveTo(localToGlobalPosition(newlocalpos), true); //TRUE means continuous motion, means adjust tangent etc. //Done with position, now change the velocity cVector3 localvelocity = globalToLocalDirection(pcritter.Velocity); cVector3 oldlocalvelocity = new cVector3(); oldlocalvelocity.copy(localvelocity); _pskeleton.reflect(localvelocity, newoutcode); /* I rewrote the reflect code on Feb 22, 2004 for VErsion 34_3, changing it so that when you reflect off an edge or corner, you only bounce the smallest of your three velocity components. Balls stll seem to get hung up on the corner once is awhile. */ /* Now decide, depending on the pcritter's absorberflag and bounciness, how much you want to use the new localvelocity vs. the oldlocalvelocity. We decompose the new localvelocity into the tangentvelocity parallel to the wall and the normalvelocity away from the wall. Some pencil and paper drawings convince me that the tangent is half the sum of the oldlocalvelocity and the reflected new localvelocity. */ cVector3 tangentvelocity = localvelocity.add(oldlocalvelocity).mult(0.5f); cVector3 normalvelocity = localvelocity.sub(tangentvelocity); float bouncefactor = 1.0f; if (pcritter.AbsorberFlag) bouncefactor = 0.0f; else bouncefactor = pcritter.Bounciness; localvelocity = tangentvelocity.add(normalvelocity.mult(bouncefactor)); /* Maybe the rotation should depend on the kind of edge or corner. Right now let's just use critter's binormal. Don't to it to the player or viewer as it's confusing. */ if (!(cRealBox3.isFaceOutcode(newoutcode)) && //edge or corner !(pcritter.IsKindOf("cCritterViewer")) && //not viewer !(pcritter.IsKindOf("cCritterArmedPlayer"))) //Not player. Note that cPlayer inherits from cCritterArmedPlayer, //so don't use cCritterPlayer as the base class here. { localvelocity.rotate(new cSpin( Framework.randomOb.randomReal( -cCritterWall.CORNERJIGGLETURN, cCritterWall.CORNERJIGGLETURN), //A random turn pcritter.Binormal)); //Around the critter's binormal localvelocity.multassign(cCritterWall.CORNERJIGGLEKICK); //Goose it a little } pcritter.Velocity = localToGlobalDirection(localvelocity); return true; }
/* Uses _foveaproportion which lies between 0.0 and 1.0. We say something is visible if it * appears on the inner _foveaproportion central box of the viewer's image screen, * which is what you see in the view window. */ //overloads //Use _pownerview to get the CpopDoc and get the cGame from that. public override void update(ACView pactiveview, float dt) { base.update(pactiveview, dt); ZClipPlanes = Game.Border.outerBox(cSprite.MAXPRISMDZ); if (_trackplayer && !((Listener != null) && Listener.RuntimeClass == "cListenerViewerRide")) /* The meaning of the visibleplayer() condition is that it doesn't make sense * to track the player if it's not an onscreen player. The reason for the * listener condition is that you don't want to stare at the player when * riding it. */ /* I should explain that the goal here is to not bother turning when the player * is moving around in the middle of the veiw area, and only to turn when he's near * the edge, but to have the turning when he's near the edge be smoooth. * The use of the 0.85 foveaproportion parameter means that you react before the player * gets right up to the edge. The reactproportion factor in lookAtProportional and * moveToProportional is delicate and should probably be adjusted according to the * current player speed relative to the visible window. The issue is that (a) if I make * reactproportion too small, like 0.01, then the viewer doesn't turn (or move) fast * enough to catch up with the player and keep it in view, but (b) if I make reactpropotion * too big, like 0.5, then the turning or moving is such an abrupt jump that the visual * effect is jerky. The goal is to do turns that are just big enough to not look jerky, * but to have the turns be big enough so you aren't turning more often than you really * have to. Another downside of a toosmall reactproportion, by the way, is that it can be * computationally expensive to react. * The way we finally solved this is to do a while loop to turn just * far enough, moving just a little at a time so as to not overshoot. */ { if (isVisible(Game.Player.Position)) // Uses _foveaproportion { _lastgoodplayeroffset = Position.sub(Game.Player.Position); } /*I'm not sure about constantly changing _lastgoodplayeroffset. On the * one hand, the offset I set in setViewpoint was a standard good one, so why * not keep it. On the other, if I want to move my viewpoint around then I * do want to be able to get a new value here. It seems ok for now.*/ else //not visible, so do somehting about it. { int loopcount = 0; /* Never have a while loop without a loopcount * to make sure you don't spin inside the while forever under some * unexpected situation like at startup. */ cVector3 lookat = Game.Player.Position; cVector3 viewerpos = lookat.add(_lastgoodplayeroffset); if (Game.worldShape() == cGame.SHAPE_XSCROLLER) { lookat = new cVector3(Game.Player.Position.X, Game.Border.Midy, Game.Player.Position.Z); viewerpos = new cVector3(lookat.X, Position.Y, Position.Z); } if (Game.worldShape() == cGame.SHAPE_YSCROLLER) { lookat = new cVector3(Game.Border.Midx, Game.Player.Position.Y, Game.Player.Position.Z); viewerpos = new cVector3(Position.X, lookat.Y, Position.Z); } if (_perspective) { while (!isVisible(lookat) && loopcount < 100) // Uses _foveaproportion { moveToProportional(viewerpos, cCritterViewer.TURNPROPORTION); loopcount++; } } else //ortho case { while (!isVisible(lookat) && loopcount < 100) // Uses _foveaproportion { moveToProportional(lookat.add(Game.Player.Binormal.mult(10.0f)), cCritterViewer.TURNPROPORTION); loopcount++; } } } } //Possibly ride the player. if (Listener.IsKindOf("cListenerViewerRide")) { cCritter pplayer = Game.Player; cVector3 offset = ((cListenerViewerRide)Listener).Offset; moveTo(pplayer.Position.add( pplayer.AttitudeTangent.mult(offset.X).add( pplayer.AttitudeNormal.mult(offset.Y).add( pplayer.AttitudeBinormal.mult(offset.Z))))); cRealBox3 skeleton = pplayer.MoveBox; if (skeleton.ZSize < 0.5f) { skeleton.setZRange(0.0f, offset.Z); } if (skeleton.YSize < 0.5f) { skeleton.setYRange(0.0f, offset.Z); } skeleton.clamp(_position); for (int i = 0; i < Game.Biota.count(); i++) { cCritter pother = Game.Biota.GetAt(i); if (pother.IsKindOf("cCritterWall")) { pother.collide(this); } } /* colliding with the wall may have twisted the viwer's orientation, * so align it once again. */ Attitude = pplayer.Attitude; /* Before we call lookAt, * make sure your attitude matches the player. For one thing, * you may have gotten twisted around in the COLLIDEVIEWER code. */ lookAt(pplayer.Position.add( pplayer.AttitudeTangent.mult(cListenerViewerRide.PLAYERLOOKAHEAD * pplayer.Radius)) ); /* This has the effect that as offset gets large you change your * looking direction see right in front of the player. The multiplier * cCritterViewer.PLAYERLOOKAHEAD is tweaked to work well * with the default cCritterViewer.OFFSET. */ } }
public void setViewpoint(cVector3 toViewer, cVector3 lookatPoint, bool trytoseewholeworld = true) { //First do some default setup stuff cVector3 toviewer = new cVector3(); toviewer.copy(toViewer); cVector3 lookatpoint = new cVector3(); lookatpoint.copy(lookatPoint); _fieldofviewangle = cCritterViewer.STARTFIELDOFVIEWANGLE; Speed = 0.0f; _attitude = new cMatrix3(new cVector3(0.0f, 0.0f, -1.0f), new cVector3(-1.0f, 0.0f, 0.0f), new cVector3(0.0f, 1.0f, 0.0f), new cVector3(0.0f, 0.0f, 0.0f)); /* To get a reasonable default orientation, we arrange the viewer axes so that: * viewer x axis = world -z axis, viewer y axis = world -x axis, viewer z axis = world y axis. * We pick this orientation so that if the viewer moves "forward" (along its tangent vector) * it moves towards the world. (We correct the mismatch between the coordinate systems in the * cCritterViewer.loadViewMatrix method, which has a long comment about this.) * Note that we will adjust _position (fourth column) later in this call * with a moveTo, also we may rotate the _attitude a bit. */ if (!_perspective) //Ortho view, simply move up. { _proportionofworldtoshow = 1.0f; //Show all of a flat world. moveTo(lookatpoint.add((new cVector3(0.0f, 0.0f, 1.0f)).mult(cCritterViewer.ORTHOZOFFSET))); // Get above the world _maxspeed = _maxspeedstandard = 0.5f * cCritterViewer.ORTHOZOFFSET; //Mimic perspective case. } else //_perspective { if (toviewer.IsPracticallyZero) //Not usable, so pick a real direction. { toviewer.copy(new cVector3(0.0f, 0.0f, 1.0f)); //Default is straight up. } if (trytoseewholeworld) /* Treat toviewer as a direction, and back off in that direction * enough to see the whole world */ { toviewer.normalize(); //Make it a unit vector. _proportionofworldtoshow = cCritterViewer.PROPORTIONOFWORLDTOSHOW; //Trying to show all of a world when flying around it, often leaves too big a space around it. float furthestcornerdistance = Game.Border.maxDistanceToCorner(lookatpoint); float tanangle = (float)Math.Tan(_fieldofviewangle / 2.0f); /* We work with half the fov in this calculation, * the tanangle will be the ratio of visible distance to distance above the world, * that is, tanangle = dr/dz, where * Our dr is _proportionofworldtoshow * furthestcornerdistance, and * our dz is the unknown seeallz height we need to back off to. * Swap tangangle and dz to get the next formula. */ float seeallz = _proportionofworldtoshow * furthestcornerdistance / tanangle; moveTo(lookatpoint.add(toviewer.mult(seeallz))); } else /*Not trytoseewholeworld. In this case we don't normalize toviewer, instead * we treat it as a displacment from the lookatpoint. */ { moveTo(lookatpoint.add(toviewer)); } lookAt(lookatpoint); _maxspeed = _maxspeedstandard = 0.5f * (Position.sub(lookatpoint)).Magnitude; /* Define the speed like this so it typically takes two seconds (1/0.5) * to fly in to lookatpoint. */ _lastgoodplayeroffset = Position.sub(Game.Player.Position); } }