MOGRE Intermediate Tutorial 4: Volume Selection and Basic Manual Objects

Note: This tutorial converted from OGRE and work with MOGRE SDK 1.7.2.
Any problems you encounter while working with this tutorial should be posted to the MOGRE Forum.

Introduction

In this tutorial we will be covering how to do volume selection. The idea is that when you click and drag the mouse across the screen, a white rectangle will trace the area you are selecting. When you let go of the mouse, all objects within the selection area will be highlighted. In order to accomplish this we will be learning how to use two objects: ManualObject (to create the rectangle) and PlaneBoundedVolumeListSceneQuery. Note that while we will cover some basic uses of ManualObject, this is only an introduction to it, and not a tutorial on how to completely create 3D objects with it. We will only cover what we need to use.

You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.

Prerequisites

Create a .cs file in the IDE of your choice and

[+] add the following code to it


Be sure this code compiles before continuing. Press escape to exit.

ManualObjects

A Crash Course in 3D Objects


Before we start diving directly into making a mesh, it would probably be useful to talk about what a mesh is, and what it is made up of. Though this is a gross oversimplification, a mesh consists of roughly two parts: the vertex buffer and the index buffer.

Vertex buffers define points in 3D space. Each element in the vertex buffer is defined by several attributes you can set. The only attribute you must set is the position of the vertex. Aside from that, there are many optional properties you can set, such as the color of the vertex, the texture coordinates, and so on. Which ones you will actually need to use is dependent on what you are trying to do with the mesh.

Index buffers "connect the dots" by selecting points from the vertex buffer. Every three indexes specified in the index buffer defines a single triangle to be drawn by the GPU. The order in which you select vertices in the index buffer tells the graphics card which way the triangle faces. A triangle which is drawn counter-clockwise is facing you, one drawn clockwise is facing away from you. Normally only the front of a triangle is rendered, so it is important to be sure that your triangles are setup properly.

Though all meshes have a vertex buffer, not all meshes will have an index buffer. For example, the mesh we are about to create will not have an index buffer since we want to create an empty rectangle (as opposed to a filled rectangle). Lastly, note that vertex and index buffers are usually stored in the video card's own memory, so your software can just send the card one simple, discrete set of commands to tell it to use those predefined buffers to render an entire 3D mesh in one go.

Introduction

There are two ways to create your own mesh within Ogre. The first way is to subclass the SimpleRenderable object and provide it with the vertex and index buffers directly. This is the most direct way to create one, but it's also the most cryptic. The Generating A Mesh code snippet shows an example of this. To make things easier, Ogre provides a much nicer interface called ManualObject, which allows you to use some simple functions to define a mesh instead of writing raw data to the buffer objects. Instead of dropping the position, color, and so on into a buffer, you simply call the "position" and "colour" functions.

In this tutorial we need to create a white rectangle to display when we are dragging the mouse to select objects. There really isn't a class in Ogre we could use to display a 2D rectangle. We will have to come up with a way of doing it on our own. We could use an Overlay and resize it to display the selection rectangle, but the problem with doing it this way is that the image you use for the selection rectangle could get stretched out of shape and look awkward. Instead, we will generate a very simple 2D mesh to act as our selection rectangle.

The Code

When we create the selection rectangle, we have to create it such that it will render in 2D. We also have to be sure that it will render when Ogre's Overlays render so that it sits on top of all other objects on screen. Doing this is actually very easy. Find the SelectionRectangle's constructor and add the following code:

RenderQueueGroup = (byte)RenderQueueGroupID.RENDER_QUEUE_OVERLAY; // when using this, ensure Depth Check is Off in the material
UseIdentityProjection = true;
UseIdentityView = true;
QueryFlags = 0;


The first function sets the render queue for the object to be the Overlay queue. The next two functions sets the projection and view matrices to be the identity. Projection and view matrices are used by many rendering systems (such as OpenGL and DirectX) to define where objects go in the world. Since Ogre abstracts this away for us, we won't go into any detail about what these matrices actually are or what they do. Instead what you need to know is that if you set the projection and view matrix to be the identity, as we have here, we will basically create a 2D object. When defining this object, the coordinate system changes a bit. We no longer deal with the Z axis (if you are asked for the Z axis, set the value to -1). Instead we have a new coordinate system with X and Y running from -1 to 1 inclusive. Lastly, we will set the query flags for the object to be 0, which will prevent prevent the selection rectangle from being included in the query results.

Cursor

Now that the object is set up, we need to actually build the rectangle. But in first, to show cursor we need to attach some GUI system (but no one work with MOGRE 1.7x at this time) or draw cursor manually or use OS capabilities. The last is simplest and we implement this with MOIS:
override method CreateInput to use non-exclusive and buffered modes.

[+] Add to class VolumeSelectionApplication


We have one small snag before we get started. We are going to be calling setCorners function with mouse locations. That is we will be given, a number between 0 and 1 for the x and y coordinates, yet we need to convert these to numbers in the range -1, 1. To make matters slightly more complicated, the y coordinate is backwards too. The mouse cursor in our case defines the top of the screen at 0, the bottom at 1. In our new coordinate system, the top of the screen is +1, the bottom is -1. Thankfully, a few quick conversions will take care of this problem. Find the setCorners function and add the following:

left = left * 2 - 1;
right = right * 2 - 1;
top = 1 - top * 2;
bottom = 1 - bottom * 2;


Now the positions are in the new coordinate system. Next we need to actually build the object. To do this, we first call the begin method. It takes in two parameters, the name of the material to use for this section of the object, and the render operation to use to draw it. Since we are not putting a texture on this, we will leave the material blank. The second parameter is the RenderOperation. We can render the mesh using points, lines, or triangles. We would use triangles if we were rendering a full mesh, but since we want an empty rectangle, we will use the line strip. The line strip draws a line to each vertex from the previous vertex you defined. So to create our rectangle, we will define 5 points (the first and the last point are the same to connect the entire rectangle):

Clear();
Begin("", RenderOperation.OperationTypes.OT_LINE_STRIP);
Position(left, top, -1);
Position(right, top, -1);
Position(right, bottom, -1);
Position(left, bottom, -1);
Position(left, top, -1);
End();

Note that since we will be calling this many times, we have added the clear call at the beginning to remove the previous rectangle before redrawing it. When defining a manual object, you may call begin/end multiple times to create multiple sub-meshes (which can have different materials/RenderOperations). Note we have also set the Z parameter to be -1, since we are trying to define a 2D object which will not use that axis. Setting it to be -1 will ensure that we are not on top of, or behind, the camera when rendering.

The last thing we need to do is set the bounding box for this object. Many SceneManagers cull objects which are off screen. Even though we've basically created a 2D object, Ogre is still a 3D engine, and treats our 2D object as if it sits in 3D space. This means that if we create this object and attach it to a SceneNode (as we will do in the next section), it will disappear on us when we look away. To fix this we will set the bounding box of the object to be infinite, so that the camera will always be inside of it:

BoundingBox.SetInfinite();

Be sure to note that we have added this code after the clear call. Every time you call ManualObject.Clear(), the bounding box is reset, so be careful if you create another ManualObject which is cleared often because the bounding box will have to be set every time you recreate it.

That's all we have to do for the SelectionRectangle class. Be sure your code compiles before continuing, but note that no functionality has changed in the program yet.

Volume Selection

Setup

Before we can jump into the selection code, we first have to setup a few things. First of all, we need to create an instance of the SelectionRectangle class, and have the SceneManager create a volume query for us. Add the following code to the CreateScene():

mRect = new SelectionRectangle("Selection SelectionRectangle");
sceneMgr.RootSceneNode.CreateChildSceneNode().AttachObject(mRect);

Probably, we need (??) to be sure the frame listener cleans up after itself when we are done. Add to the VolumeSelectionApplication DestroyScene method or finalizer:

public override void DestroyScene()
{
    sceneMgr.DestroyQuery(mVolQuery);
    mRect.Dispose();
}

Note that we let the SceneManager clean up the query for us.

Mouse Handlers

The feature we are trying to implement is volume selection. This means that when the user clicks the mouse down and drags it across the screen a rectangle will be drawn. When they let go of the mouse, all objects within the rectangle will be selected. The first thing we will need to do is handle when the mouse is pressed. We'll need to store the starting location and set the the SelectionRectangle to be visible. Find the mousePressed function and add the following code:

switch (button)
{
    case MOIS.MouseButtonID.MB_Left:
        mStart.x = (float)e.state.X.abs / (float)e.state.width;
        mStart.y = (float)e.state.Y.abs / (float)e.state.height;
        mStop = mStart;

        bSelecting = true;
        mRect.Clear();
        mRect.Visible = true;
        break;
}


Note that in some GUI systems, x and y coordinates of displayed cursor may not be equal to OIS's mouse coordinates.

The next thing we will need to do is stop displaying the selection rectangle and perform the selection query when the user releases the mouse. Add the following code to the mouseReleased code:

switch (button)
{
    case MOIS.MouseButtonID.MB_Left:
        performSelection(mStart, mStop);
        bSelecting = false;
        mRect.Visible = false;
        break;
}

Finally, every time the mouse is moved, we need to update the rectangle to the new coordinates. Add to MouseMotion:

if (bSelecting)
        {
            mStop.x = e.state.X.abs / (float)e.state.width;
            mStop.y = e.state.Y.abs / (float)e.state.height;

            mRect.setCorners(mStart, mStop);
        }

We adjust the mStop vector every time the mouse is moved so that we can simply use it for the setCorners member function. Compile and run your application. You can now draw a rectangle using the mouse.

PlaneBoundedVolumeListSceneQuery

Now that we have the SelectionRectangle properly rendering, we need to actually perform the volume selection. Find the performSelection function and add the following code:

float left = first.x, right = second.x,
            top = first.y, bottom = second.y;

            if (left > right) swap(ref left, ref right);
            if (top > bottom) swap(ref top, ref bottom);

In this code section, we have assigned the vector parameters into the left, right, top, and bottom variables. The if statements ensure that we actually have the lowest value in left and top. (If the rectangle is drawn "backwards", meaning bottom-right to top-left, then we have to perform this swap.)

Next, we have to check and see how big of an area the rectangle actually makes. If the rectangle is too small, our method of creating a plane bound volumes will fail and we will end up selecting too many or too few objects. If the rectangle is less than a certain percentage of the screen, we will simply return and not perform the selection. I have arbitrarily selected 0.0001 as the threshold for canceling the query, though you should probably play around with this value for your own program. Also, in a real application you should probably find the center of the rectangle and perform a standard RaySceneQuery (as explained in previous tutorial) instead of doing nothing:

if ((right - left) * (bottom - top) < 0.0001) return;

Now we are at the meat of the function, and we need to perform the query itself. PlaneBoundedVolumeQueries use planes to enclose an area, then select any objects inside that area. For this example we will build an area enclosed by five planes which face inward. To create these planes out of our rectangle, we will create 4 rays, one for each corner of the rectangle. Once we have these four rays, we will grab points along the rays to create the planes:

Ray topLeft = camera.GetCameraToViewportRay(left, top);
Ray topRight = camera.GetCameraToViewportRay(right, top);
Ray bottomLeft = camera.GetCameraToViewportRay(left, bottom);
Ray bottomRight = camera.GetCameraToViewportRay(right, bottom);

Now we will create the planes. Note that we are grabbing a point 100 units along the ray. This was chosen fairly arbitrarily. We could have chosen 2 instead of 100. The only point which matters here is the front plane, which we are starting 3 units in front of the Camera.

PlaneBoundedVolume vol=new PlaneBoundedVolume();
vol.planes.Add(new Plane(topLeft.GetPoint(3), topRight.GetPoint(3), bottomRight.GetPoint(3)));    // front plane
vol.planes.Add(new Plane(topLeft.Origin, topLeft.GetPoint(100), topRight.GetPoint(100)));         // top plane
vol.planes.Add(new Plane(topLeft.Origin, bottomLeft.GetPoint(100), topLeft.GetPoint(100)));       // left plane
vol.planes.Add(new Plane(bottomLeft.Origin, bottomRight.GetPoint(100), bottomLeft.GetPoint(100)));// bottom plane
vol.planes.Add(new Plane(topRight.Origin, topRight.GetPoint(100), bottomRight.GetPoint(100)));    // right plane

These planes have now defined an "open box" which extends to infinity in front of the camera. You can think of the rectangle we drew with the mouse as being the termination point of the box just in front of the camera. Now that we have created the planes, we need to execute the query:

PlaneBoundedVolumeList volList = new PlaneBoundedVolumeList();
volList.Add(vol);
if(mVolQuery!=null)
{
mVolQuery.SetVolumes(volList);
}
else
{
mVolQuery = sceneMgr.CreatePlaneBoundedVolumeQuery(volList);
}
SceneQueryResult result = mVolQuery.Execute();

Finally we need to handle the results of the query. First we will deselect all previously selected objects, then we will select all objects which were found by the query. The deselectObjects and selectObject functions have already been filled in for you since we have covered everything in those functions in a previous tutorial:

foreach (var obj in result.movables) selectObject(obj);

That's all we need to do for the query. Note we can also use query flags on volume queries, even though we have not done so in this tutorial. See the previous tutorial for more information on query flags.

Compile and run the application. You can now volume select objects in the scene!

A Final Note About Bounding Boxes

As you may have noticed from this tutorial and the previous two tutorials, selection in Ogre relies on the bounding box of the objects in question and not on the mesh itself. This means that a RaySceneQuery and PlaneBoundedVolumeQuery will always be too accepting in what is actually hit by the query. There are ways of performing pixel perfect ray selection (such as what you would need to do to see if a gunshot laser beam in an FPS actually hit its target) and volume selection which will give you perfectly accurate results for a trade-off in speed. Unfortunately, this is beyond the scope of this tutorial. Take a look at Raycasting to the polygon level for more information on how to do this in pure Ogre. If you have integrated Ogre with a physics library, such as OgreNewt, they should also provide methods to do this for you.

You did not learn all of this about ray queries and volume queries for nothing though. Doing a mesh based selection is very time consuming and can quickly kill your framerate if you try to check everything in a scene. In fact, the most common way to do "true" mouse selection is to first perform an Ogre query (such as a RaySceneQuery), then individually test each intersection returned from the query with a physics engine that will actually check the mesh's geometry to see if you actually hit it or just came very close.


Alias: MOGRE_Intermediate_Tutorial_4

<HR>
Creative Commons Copyright -- Some rights reserved.


THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.

1. Definitions

  • "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
  • "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
  • "Licensor" means the individual or entity that offers the Work under the terms of this License.
  • "Original Author" means the individual or entity who created the Work.
  • "Work" means the copyrightable work of authorship offered under the terms of this License.
  • "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
  • "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.

2. Fair Use Rights

Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.

3. License Grant

Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:

  • to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
  • to create and reproduce Derivative Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
  • For the avoidance of doubt, where the work is a musical composition:
    • Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
    • Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
    • Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).


The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.

4. Restrictions

The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:

  • You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
  • You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
  • If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability.

EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

  • This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
  • Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.

8. Miscellaneous

  • Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
  • Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
  • If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
  • No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
  • This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.