eve

package
v0.6.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2020 License: BSD-3-Clause Imports: 7 Imported by: 3

Documentation

Index

Constants

View Source
const (
	// DynsTopGps is passed to WorldCollide when all dynamic objects are in separate top groups
	DynsTopGps = true

	// DynsSubGps is passed to WorldCollide when all dynamic objects are in separate groups under top
	// level (i.e., one level deeper)
	DynsSubGps
)
View Source
const (
	Version     = "v0.6.5"
	GitCommit   = "93fbcee"          // the commit JUST BEFORE the release
	VersionDate = "2020-09-04 22:16" // UTC
)
View Source
const AngMotionMax = math.Pi / 4

AngMotionMax is maximum angular motion that can be taken per update

Variables

View Source
var BoxProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
}
View Source
var CapsuleProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
}
View Source
var CylinderProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
}
View Source
var GroupProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
	"ToolBar": ki.PropSlice{
		{"WorldInit", ki.Props{
			"desc": "initialize all elements in the world.",
			"icon": "reset",
		}},
	},
}

GroupProps define the ToolBar and MenuBar for StructView

View Source
var KiT_Box = kit.Types.AddType(&Box{}, BoxProps)
View Source
var KiT_Capsule = kit.Types.AddType(&Capsule{}, CapsuleProps)
View Source
var KiT_Cylinder = kit.Types.AddType(&Cylinder{}, CylinderProps)
View Source
var KiT_Group = kit.Types.AddType(&Group{}, GroupProps)
View Source
var KiT_NodeBase = kit.Types.AddType(&NodeBase{}, NodeBaseProps)
View Source
var KiT_NodeFlags = kit.Enums.AddEnumExt(ki.KiT_Flags, NodeFlagsN, kit.BitFlag, nil)
View Source
var KiT_NodeTypes = kit.Enums.AddEnum(NodeTypesN, kit.NotBitFlag, nil)
View Source
var KiT_Phys = kit.Types.AddType(&Phys{}, PhysProps)
View Source
var KiT_Rigid = kit.Types.AddType(&Rigid{}, RigidProps)
View Source
var KiT_Sphere = kit.Types.AddType(&Sphere{}, SphereProps)
View Source
var NodeBaseProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
}
View Source
var PhysProps = ki.Props{
	"ToolBar": ki.PropSlice{
		{"SetEulerRotation", ki.Props{
			"desc": "Set the local rotation (relative to parent) using Euler angles, in degrees.",
			"icon": "rotate-3d",
			"Args": ki.PropSlice{
				{"Pitch", ki.Props{
					"desc": "rotation up / down along the X axis (in the Y-Z plane), e.g., the altitude (climbing, descending) for motion along the Z depth axis",
				}},
				{"Yaw", ki.Props{
					"desc": "rotation along the Y axis (in the horizontal X-Z plane), e.g., the bearing or direction for motion along the Z depth axis",
				}},
				{"Roll", ki.Props{
					"desc": "rotation along the Z axis (in the X-Y plane), e.g., the bank angle for motion along the Z depth axis",
				}},
			},
		}},
		{"SetAxisRotation", ki.Props{
			"desc": "Set the local rotation (relative to parent) using Axis about which to rotate, and the angle.",
			"icon": "rotate-3d",
			"Args": ki.PropSlice{
				{"X", ki.BlankProp{}},
				{"Y", ki.BlankProp{}},
				{"Z", ki.BlankProp{}},
				{"Angle", ki.BlankProp{}},
			},
		}},
		{"RotateEuler", ki.Props{
			"desc": "rotate (relative to current rotation) using Euler angles, in degrees.",
			"icon": "rotate-3d",
			"Args": ki.PropSlice{
				{"Pitch", ki.Props{
					"desc": "rotation up / down along the X axis (in the Y-Z plane), e.g., the altitude (climbing, descending) for motion along the Z depth axis",
				}},
				{"Yaw", ki.Props{
					"desc": "rotation along the Y axis (in the horizontal X-Z plane), e.g., the bearing or direction for motion along the Z depth axis",
				}},
				{"Roll", ki.Props{
					"desc": "rotation along the Z axis (in the X-Y plane), e.g., the bank angle for motion along the Z depth axis",
				}},
			},
		}},
		{"RotateOnAxis", ki.Props{
			"desc": "Rotate (relative to current rotation) using Axis about which to rotate, and the angle.",
			"icon": "rotate-3d",
			"Args": ki.PropSlice{
				{"X", ki.BlankProp{}},
				{"Y", ki.BlankProp{}},
				{"Z", ki.BlankProp{}},
				{"Angle", ki.BlankProp{}},
			},
		}},
		{"EulerRotation", ki.Props{
			"desc":        "The local rotation (relative to parent) in Euler angles in degrees (X = Pitch, Y = Yaw, Z = Roll)",
			"icon":        "rotate-3d",
			"show-return": "true",
		}},
		{"sep-rot", ki.BlankProp{}},
		{"MoveOnAxis", ki.Props{
			"desc": "Move given distance on given X,Y,Z axis relative to current rotation orientation.",
			"icon": "pan",
			"Args": ki.PropSlice{
				{"X", ki.BlankProp{}},
				{"Y", ki.BlankProp{}},
				{"Z", ki.BlankProp{}},
				{"Dist", ki.BlankProp{}},
			},
		}},
		{"MoveOnAxisAbs", ki.Props{
			"desc": "Move given distance on given X,Y,Z axis in absolute coords, not relative to current rotation orientation.",
			"icon": "pan",
			"Args": ki.PropSlice{
				{"X", ki.BlankProp{}},
				{"Y", ki.BlankProp{}},
				{"Z", ki.BlankProp{}},
				{"Dist", ki.BlankProp{}},
			},
		}},
	},
}

PhysProps define the ToolBar and MenuBar for StructView

View Source
var RigidProps = ki.Props{}

RigidProps define the ToolBar and MenuBar for StructView

View Source
var SphereProps = ki.Props{
	"EnumType:Flag": KiT_NodeFlags,
}

Functions

func KiToNode

func KiToNode(k ki.Ki) (Node, *NodeBase)

KiToNode converts Ki to a Node interface and a Node3DBase obj -- nil if not.

Types

type BBox

type BBox struct {
	BBox    mat32.Box3   `desc:"bounding box in world coords (Axis-Aligned Bounding Box = AABB)"`
	VelBBox mat32.Box3   `` /* 151-byte string literal not displayed */
	BSphere mat32.Sphere `desc:"bounding sphere in local coords"`
	Area    float32      `desc:"area"`
	Volume  float32      `desc:"volume"`
}

BBox contains bounding box and other gross object properties

func (*BBox) IntersectsVelBox

func (bb *BBox) IntersectsVelBox(oth *BBox) bool

IntersectsVelBox returns true if two velocity-projected bounding boxes intersect

func (*BBox) SetBounds

func (bb *BBox) SetBounds(min, max mat32.Vec3)

SetBounds sets BBox from min, max and updates other factors based on that

func (*BBox) UpdateFmBBox

func (bb *BBox) UpdateFmBBox()

UpdateFmBBox updates other values from BBox

func (*BBox) VelNilProject

func (bb *BBox) VelNilProject()

VelNilProject is for static items -- just copy the BBox

func (*BBox) VelProject

func (bb *BBox) VelProject(vel mat32.Vec3, step float32)

VelProject computes the velocity-projected bounding box for given velocity and step size

func (*BBox) XForm

func (bb *BBox) XForm(q mat32.Quat, pos mat32.Vec3)

XForm transforms bounds with given quat and position offset to convert to world coords

type Body

type Body interface {
	Node

	// AsBodyBase returns the body as a BodyBase
	AsBodyBase() *BodyBase

	// SetDynamic sets the Dynamic flag for this body, indicating that it moves.
	// It is important to collect all dynamic objects into separate top-level group(s)
	// for more efficiently organizing the collision detection process.
	SetDynamic()
}

Body is the common interface for all body types

type BodyBase

type BodyBase struct {
	NodeBase
	Rigid Rigid  `desc:"rigid body properties, including mass, bounce, friction etc"`
	Vis   string `desc:"visualization name -- looks up an entry in the scene library that provides the visual representation of this body"`
	Color string `desc:"default color of body for basic InitLibrary configuration"`
}

BodyBase is the base type for all specific Body types

func (*BodyBase) AsBody

func (bb *BodyBase) AsBody() Body

func (*BodyBase) AsBodyBase

func (bb *BodyBase) AsBodyBase() *BodyBase

func (*BodyBase) GroupBBox

func (bb *BodyBase) GroupBBox()

func (*BodyBase) NodeType

func (bb *BodyBase) NodeType() NodeTypes

func (*BodyBase) SetDynamic

func (bb *BodyBase) SetDynamic()

type BodyPoint added in v0.6.1

type BodyPoint struct {
	Body  Body
	Point mat32.Vec3
}

BodyPoint contains a Body and a Point on that body

type Box

type Box struct {
	BodyBase
	Size mat32.Vec3 `desc:"size of box in each dimension (units arbitrary, as long as they are all consistent -- meters is typical)"`
}

Box is a box body shape

func AddNewBox

func AddNewBox(parent ki.Ki, name string, pos, size mat32.Vec3) *Box

AddNewBox adds a new box of given name, initial position and size to given parent

func (*Box) InitAbs

func (bx *Box) InitAbs(par *NodeBase)

func (*Box) RelToAbs

func (bx *Box) RelToAbs(par *NodeBase)

func (*Box) SetBBox

func (bx *Box) SetBBox()

func (*Box) StepPhys

func (bx *Box) StepPhys(step float32)

type Capsule

type Capsule struct {
	BodyBase
	Height float32 `desc:"height of the cylinder portion of the capsule"`
	TopRad float32 `desc:"radius of the top hemisphere"`
	BotRad float32 `desc:"radius of the bottom hemisphere"`
}

Capsule is a generalized cylinder body shape, with hemispheres at each end, with separate radii for top and bottom.

func AddNewCapsule

func AddNewCapsule(parent ki.Ki, name string, pos mat32.Vec3, height, radius float32) *Capsule

AddNewCapsule adds a new capsule of given name, initial position and height, radius to given parent.

func (*Capsule) InitAbs

func (cp *Capsule) InitAbs(par *NodeBase)

func (*Capsule) RelToAbs

func (cp *Capsule) RelToAbs(par *NodeBase)

func (*Capsule) SetBBox

func (cp *Capsule) SetBBox()

func (*Capsule) StepPhys

func (cp *Capsule) StepPhys(step float32)

type Contact

type Contact struct {
	A     Body       `desc:"one body"`
	B     Body       `desc:"the other body"`
	NormB mat32.Vec3 `desc:"normal pointing from center of B to center of A"`
	PtB   mat32.Vec3 `desc:"point on spherical shell of B where A is contacting"`
	Dist  float32    `desc:"distance from PtB along NormB to contact point on spherical shell of A"`
}

Contact is one pairwise point of contact between two bodies. Contacts are represented in spherical terms relative to the spherical BBox of A and B.

func (*Contact) UpdtDist

func (c *Contact) UpdtDist()

UpdtDist updates the distance information for the contact

type Contacts

type Contacts []*Contact

Contacts is a slice list of contacts

func BodyVelBBoxIntersects

func BodyVelBBoxIntersects(a, b Node) Contacts

BodyVelBBoxIntersects returns the list of potential contact nodes between a and b (could be the same or different groups) that have intersecting velocity-projected bounding boxes. In general a should be dynamic bodies and b either dynamic or static. This is the broad first-pass filtering.

func (*Contacts) AddNew

func (cs *Contacts) AddNew(a, b Body) *Contact

AddNew adds a new contact to the list

type Cylinder

type Cylinder struct {
	BodyBase
	Height float32 `desc:"height of the cylinder"`
	TopRad float32 `desc:"radius of the top -- set to 0 for a cone"`
	BotRad float32 `desc:"radius of the bottom"`
}

Cylinder is a generalized cylinder body shape, with separate radii for top and bottom. A cone has a zero radius at one end.

func AddNewCone

func AddNewCone(parent ki.Ki, name string, pos mat32.Vec3, height, radius float32) *Cylinder

AddNewCone adds a new cone of given name, initial position and height, radius to given parent.

func AddNewCylinder

func AddNewCylinder(parent ki.Ki, name string, pos mat32.Vec3, height, radius float32) *Cylinder

AddNewCylinder adds a new cylinder of given name, initial position and height, radius to given parent.

func (*Cylinder) InitAbs

func (cy *Cylinder) InitAbs(par *NodeBase)

func (*Cylinder) RelToAbs

func (cy *Cylinder) RelToAbs(par *NodeBase)

func (*Cylinder) SetBBox

func (cy *Cylinder) SetBBox()

func (*Cylinder) StepPhys

func (cy *Cylinder) StepPhys(step float32)

type Group

type Group struct {
	NodeBase
}

Group is a container of bodies, joints, or other groups it should be used strategically to partition the space and its BBox is used to optimize tree-based collision detection. Use a group for the top-level World node as well.

func AddNewGroup

func AddNewGroup(parent ki.Ki, name string) *Group

AddNewGroup adds a new group of given name to given parent

func (*Group) GroupBBox

func (gp *Group) GroupBBox()

func (*Group) InitAbs

func (gp *Group) InitAbs(par *NodeBase)

func (*Group) NodeType

func (gp *Group) NodeType() NodeTypes

func (*Group) RayBodyIntersections added in v0.6.1

func (gp *Group) RayBodyIntersections(ray mat32.Ray) []*BodyPoint

RayBodyIntersections returns a list of bodies whose bounding box intersects with the given ray, with the point of intersection

func (*Group) RelToAbs

func (gp *Group) RelToAbs(par *NodeBase)

func (*Group) StepPhys

func (gp *Group) StepPhys(step float32)

func (*Group) WorldCollide

func (gp *Group) WorldCollide(dynTop bool) []Contacts

WorldCollide does first pass filtering step of collision detection based on separate dynamic vs. dynamic and dynamic vs. static groups. If dynTop is true, then each Dynamic group is separate at the top level -- otherwise they are organized at the next group level. Contacts are organized by dynamic group, when non-nil, for easier processing.

func (*Group) WorldDynGroupBBox

func (gp *Group) WorldDynGroupBBox()

WorldDynGroupBBox does a GroupBBox on all dynamic nodes

func (*Group) WorldInit

func (gp *Group) WorldInit()

WorldInit does the full tree InitAbs and GroupBBox updates

func (*Group) WorldRelToAbs

func (gp *Group) WorldRelToAbs()

WorldRelToAbs does a full RelToAbs update for all Dynamic groups, for Scripted mode updates with manual updating of Rel values.

func (*Group) WorldStepPhys

func (gp *Group) WorldStepPhys(step float32)

WorldStepPhys does a full StepPhys update for all Dynamic nodes, for either physics or scripted mode, based on current velocities.

type Node

type Node interface {
	ki.Ki

	// NodeType returns the type of node this is (Body, Group, Joint)
	NodeType() NodeTypes

	// AsNodeBase returns a generic NodeBase for our node -- gives generic
	// access to all the base-level data structures without needing interface methods.
	AsNodeBase() *NodeBase

	// AsBody returns a generic Body interface for our node -- nil if not a Body
	AsBody() Body

	// IsDynamic returns true if node has Dynamic flag set -- otherwise static
	// Groups that contain dynamic objects set their dynamic flags.
	IsDynamic() bool

	// GroupBBox sets bounding boxes for groups based on groups or bodies.
	// called in a FuncDownMeLast traversal.
	GroupBBox()

	// InitAbs sets current Abs physical state parameters from Initial values
	// which are local, relative to parent -- is passed the parent (nil = top).
	// Body nodes should also set their bounding boxes.
	// Called in a FuncDownMeFirst traversal.
	InitAbs(par *NodeBase)

	// RelToAbs updates current world Abs physical state parameters
	// based on Rel values added to updated Abs values at higher levels.
	// Abs.LinVel is updated from the resulting change from prior position.
	// This is useful for manual updating of relative positions (scripted movement).
	// It is passed the parent (nil = top).
	// Body nodes should also update their bounding boxes.
	// Called in a FuncDownMeFirst traversal.
	RelToAbs(par *NodeBase)

	// StepPhys computes one update of the world Abs physical state parameters,
	// using *current* velocities -- add forces prior to calling.
	// Use this for physics-based state updates.
	// Body nodes should also update their bounding boxes.
	StepPhys(step float32)
}

Node is the common interface for all eve nodes

type NodeBase

type NodeBase struct {
	ki.Node
	Initial Phys `view:"inline" desc:"initial position, orientation, velocity in *local* coordinates (relative to parent)"`
	Rel     Phys `` /* 143-byte string literal not displayed */
	Abs     Phys `inactive:"+" view:"inline" desc:"current absolute (world) position, orientation, velocity"`
	BBox    BBox `desc:"bounding box in world coordinates (aggregated for groups)"`
}

NodeBase is the basic eve node, which has position, rotation, velocity and computed bounding boxes, etc. There are only three different kinds of Nodes: Group, Body, and Joint

func (*NodeBase) AsBody

func (nb *NodeBase) AsBody() Body

func (*NodeBase) AsNodeBase

func (nb *NodeBase) AsNodeBase() *NodeBase

func (*NodeBase) InitAbsBase

func (nb *NodeBase) InitAbsBase(par *NodeBase)

InitAbsBase is the base-level version of InitAbs -- most nodes call this. InitAbs sets current Abs physical state parameters from Initial values which are local, relative to parent -- is passed the parent (nil = top). Body nodes should also set their bounding boxes. Called in a FuncDownMeFirst traversal.

func (*NodeBase) IsDynamic

func (nb *NodeBase) IsDynamic() bool

func (*NodeBase) RelToAbsBase

func (nb *NodeBase) RelToAbsBase(par *NodeBase)

RelToAbsBase is the base-level version of RelToAbs -- most nodes call this. note: Group WorldRelToAbs ensures only called on Dynamic nodes. RelToAbs updates current world Abs physical state parameters based on Rel values added to updated Abs values at higher levels. Abs.LinVel is updated from the resulting change from prior position. This is useful for manual updating of relative positions (scripted movement). It is passed the parent (nil = top). Body nodes should also update their bounding boxes. Called in a FuncDownMeFirst traversal.

func (*NodeBase) StepPhysBase

func (nb *NodeBase) StepPhysBase(step float32)

StepPhysBase is base-level version of StepPhys -- most nodes call this. note: Group WorldRelToAbs ensures only called on Dynamic nodes. Computes one update of the world Abs physical state parameters, using *current* velocities -- add forces prior to calling. Use this for physics-based state updates. Body nodes should also update their bounding boxes.

type NodeFlags

type NodeFlags int

NodeFlags define eve node bitflags -- uses ki Flags field (64 bit capacity)

const (
	// Dynamic means that this node can move -- if not so marked, it is
	// a Static node.  Any top-level group that is not Dynamic is immediately
	// pruned from further consideration, so top-level groups should be
	// separated into Dynamic and Static nodes at the start.
	Dynamic NodeFlags = NodeFlags(ki.FlagsN) + iota

	NodeFlagsN
)

func StringToNodeFlags

func StringToNodeFlags(s string) (NodeFlags, error)

func (NodeFlags) String

func (i NodeFlags) String() string

type NodeTypes

type NodeTypes int

NodeTypes is a list of node types

const (
	// note: uppercase required to not conflict with type names
	BODY NodeTypes = iota
	GROUP
	JOINT
	NodeTypesN
)

func (*NodeTypes) FromString

func (i *NodeTypes) FromString(s string) error

func (NodeTypes) String

func (i NodeTypes) String() string

type Phys

type Phys struct {
	Pos    mat32.Vec3 `desc:"position of center of mass of object"`
	Quat   mat32.Quat `desc:"rotation specified as a Quat"`
	LinVel mat32.Vec3 `desc:"linear velocity"`
	AngVel mat32.Vec3 `desc:"angular velocity"`
}

Phys contains the basic physical properties including position, orientation, velocity. These are only the values that can be either relative or absolute -- other physical state values such as Mass should go in Rigid.

func (*Phys) Defaults

func (ps *Phys) Defaults()

Defaults sets defaults only if current values are nil

func (*Phys) EulerRotation

func (ps *Phys) EulerRotation() mat32.Vec3

EulerRotation returns the current rotation in Euler angles (degrees).

func (*Phys) EulerRotationRad

func (ps *Phys) EulerRotationRad() mat32.Vec3

EulerRotationRad returns the current rotation in Euler angles (radians).

func (*Phys) FromRel

func (ps *Phys) FromRel(rel, par *Phys)

FromRel sets state from relative values compared to a parent state

func (*Phys) Move

func (ps *Phys) Move(delta mat32.Vec3)

Move moves (translates) Pos by given amount, and sets the LinVel to the given delta -- this can be useful for Scripted motion to track movement.

func (*Phys) MoveOnAxis

func (ps *Phys) MoveOnAxis(x, y, z, dist float32)

MoveOnAxis moves (translates) the specified distance on the specified local axis, relative to the current rotation orientation. The axis is normalized prior to aplying the distance factor. Sets the LinVel to motion vector.

func (*Phys) MoveOnAxisAbs

func (ps *Phys) MoveOnAxisAbs(x, y, z, dist float32)

MoveOnAxisAbs moves (translates) the specified distance on the specified local axis, in absolute X,Y,Z coordinates (does not apply the Quat rotation factor. The axis is normalized prior to aplying the distance factor. Sets the LinVel to motion vector.

func (*Phys) RotateEuler

func (ps *Phys) RotateEuler(x, y, z float32)

RotateEuler rotates by given Euler angles (in degrees) relative to existing rotation.

func (*Phys) RotateEulerRad

func (ps *Phys) RotateEulerRad(x, y, z, angle float32)

RotateEulerRad rotates by given Euler angles (in radians) relative to existing rotation.

func (*Phys) RotateOnAxis

func (ps *Phys) RotateOnAxis(x, y, z, angle float32)

RotateOnAxis rotates around the specified local axis the specified angle in degrees.

func (*Phys) RotateOnAxisRad

func (ps *Phys) RotateOnAxisRad(x, y, z, angle float32)

RotateOnAxisRad rotates around the specified local axis the specified angle in radians.

func (*Phys) SetAxisRotation

func (ps *Phys) SetAxisRotation(x, y, z, angle float32)

SetAxisRotation sets rotation from local axis and angle in degrees.

func (*Phys) SetAxisRotationRad

func (ps *Phys) SetAxisRotationRad(x, y, z, angle float32)

SetAxisRotationRad sets rotation from local axis and angle in radians.

func (*Phys) SetEulerRotation

func (ps *Phys) SetEulerRotation(x, y, z float32)

SetEulerRotation sets the rotation in Euler angles (degrees).

func (*Phys) SetEulerRotationRad

func (ps *Phys) SetEulerRotationRad(x, y, z float32)

SetEulerRotationRad sets the rotation in Euler angles (radians).

func (*Phys) StepByAngVel

func (ps *Phys) StepByAngVel(step float32)

StepByAngVel steps the Quat rotation from angular velocity

func (*Phys) StepByLinVel

func (ps *Phys) StepByLinVel(step float32)

StepByLinVel steps the Pos from the linear velocity

type Rigid

type Rigid struct {
	InvMass    float32    `desc:"1/mass -- 0 for no mass"`
	Bounce     float32    `` /* 128-byte string literal not displayed */
	Friction   float32    `desc:"friction coefficient -- how much friction is generated by transverse motion"`
	Force      mat32.Vec3 `desc:"record of computed force vector from last iteration"`
	RotInertia mat32.Mat3 `desc:"Last calculated rotational inertia matrix in local coords"`
}

Rigid contains the full specification of a given object's basic physics properties including position, orientation, velocity. These

func (*Rigid) Defaults

func (ps *Rigid) Defaults()

Defaults sets defaults only if current values are nil

type Sphere

type Sphere struct {
	BodyBase
	Radius float32 `desc:"radius"`
}

Sphere is a spherical body shape.

func AddNewSphere

func AddNewSphere(parent ki.Ki, name string, pos mat32.Vec3, radius float32) *Sphere

AddNewSphere adds a new sphere of given name, initial position and radius.

func (*Sphere) InitAbs

func (sp *Sphere) InitAbs(par *NodeBase)

func (*Sphere) RelToAbs

func (sp *Sphere) RelToAbs(par *NodeBase)

func (*Sphere) SetBBox

func (sp *Sphere) SetBBox()

func (*Sphere) StepPhys

func (sp *Sphere) StepPhys(step float32)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL