Start your journey in learning Holoscripting with these foundational documents as guides covering fundamentals and logic.
Start your journey in learning Holoscripting with these foundational documents as guides covering fundamentals and logic.
The Holoscripting dev system is a set of utilities and libraries designed to help compile and deploy a Holoscripting to a Cavrnus space. You can write scripts manually if you wish, but using these tools should make for a better experience.
Holoscripting itself is interpreted javascript. For ease of development we opt to build scripts using Typescript. The types provided by the Cavrnus runtime are very helpful for reducing programming mistakes, and also provide code-level documentation for the capabilities of Holoscripting.
You will need:
npm install --global yarn
Optional but helpful:
In your terminal of choice, run:
npx @cavrnus/cli login
This will prompt you to provide your Cavrnus login information, in order to work with your account.
If you are connecting to the Cavrnus staging environment, or an on-premises Cavrnus installation you will need to use:
npx @cavrnus/cli login -s
This will allow you to select the destination server.
For the staging environment, this is ‘api.stage.cavrn.us’, and continue.
You will be prompted for your Cavrnus customer id. If you log in to the website at ‘http://mycompany.cavrn.us ’, your id is ‘mycompany’.
Provide your username and password.
You can rerun this command to change accounts whenever you wish.
You can confirm your current login information by running:
npx @cavrnus/cli profile
Create a new folder as your scripting workspace, or use an existing one.
Open up Visual Studio Code in this empty folder.
In your terminal of choice, in your script project folder, run:
npx @cavrnus/cli new
If you’re targetting the dev or stage environments, use:
npx @cavrnus/cli new -d
This will run the Cavrnus CLI to build a new scripting project. It will ask you:
If this is your first run of the @cavrnus/cli, after setting up the new script package, the cli will prompt you to login, as per the above instructions.
After a moment or two the initialization should complete. You will see a new folder present, named the same as your script. Inside you will see:
node_modules, package.json, yarn.lock, tsconfig.json:
These are all familiar to typescript and node.js developers and contain libraries, dependencies, scripts and configuration data. For Holoscripting purposes these should not need to be changed.
script.ts: This is your script! By default it should contain:
import cav, { out } from '@cavrnus/runtime';out.print(`Welcome to Holoscripting!`);
Build and deploy your script to your selected room by running:
yarn deploy
or equivalently:
npm run deploy
If you are running the Cavrnus application and connected to the room, you will see the script appear in the objects list and your script will be live!
After deploying you may notice a .hs file produced in your script directory. This is a packaged and sharable version of the script, and can be uploaded directly inside the Cavrnus application just like any other content.
Your script is also available in your Cavrnus library. You can insert it just like any other object from the Cavrnus Insert > Content menu. As you deploy new versions, old versions will be archived. They are still usable in rooms where they are already present, but will not show up in your library.
See what you can do by inspecting the ‘cav’ object for functions and data accessors:
yarn upgrade
This is a standard node.js script to upgrade all dependencies. Run this when needed to update the dependencies and runtime types to match the Cavrnus Application.
yarn build
Compiles without incrementing the version or deploying the script.
yarn deployto
deployto will prompt you for a room to deploy the script to, instead of the default configured room.
yarn scriptenv
scriptenv will connect to the deployment room and download type information for any other scripts live in the room, and download them to your script’s library/ folder. Imports from these libraries can then be included in your script.ts source using:
import * as YourScriptName from 'yourscriptnamehere';
Holoscripting import does not support subfolder imports (e.g. import * as X from ‘yourscriptnamehere/script', so avoid that.
Deploying a script will not deploy its dependencies; if they are not present in the room then the script will not run. It will wait until the dependencies are present before starting up.
@cavrnus/runtime describes with types, the capabilities of the Cavrnus application scripting system. At the moment there is no interactive debugging capability, nor can the runtime or script be run from within node.js. We hope to build these things in the future.
Importing scripts from other Holoscripting libraries must be done with a simple library name without subfolder/files specified.
This is acceptable:
import * as test from 'test'
This is not:
import * as testinternal from 'test/internalscript'
Consider instead with the assumption that the main script exports the contents of internalscript.
import {testinternal} from ‘test';
Deploying a script will not also upload its dependencies. They will need to be inserted into the space using their own project space, or directly in the Cavrnus application.
The data that defines a space is called its Journal.
The Journal consists of a linear sequence of Operations. Operations are never deleted, the Journal only ever moves forward. Think of the Journal as a log of everything that ever occurred within the space, which when replayed, reconstructs the space in its current state.
When connected to a space, a user can submit an Operation to be included in the journal. These become part of the permanent journal. Common operations include:
A Transient, is a communication sent by a user which is not stored in the Journal. Transient communications involve either temporary or unsynchronized state changes. When a new user joins the space, they will not be transmitted any historical transient information; they only receive and process the permanent journal. Common transients include:
But the most important transient is most likely:
As an example, while a user is manipulating an object’s position, they will frequently send transient operations as they change the object’s values. When they release the object they will submit a finalized Operation. Or if they instead hit Escape or otherwise cancel, they will send a transient event cancelling the in-progress process.
Lastly, transient events are often echoed locally; they take effect immediately within the executing client’s application. Operations require guaranteed ordering, so they only ever are applied after a round trip to the Cavrnus API server. Since latency makes for bad UX, transients are applied locally immediately. The journalling system will ensure that the correct synchronized state is reached despite a potential order difference when multiple users manipulate the same fields simultaneously.
Operations are permanent changes.
Transients are temporary state transmissions and coordination systems.
For example, consider the following sequence of abstract operations:
1. Create a Sphere called 'A'.
2. Move 'A' to (10,1,0).
3. Create a Box called 'B'.
This results in a space with two objects, A and B. If we wish to remove 'B' from the space, rather than delete anything from the Journal, we instead:
4. Cancel operation #3.
Which will result in 'B' not being present, as its creation is now ignored.
Consider a user wishing to ‘Undo’ the move of 'A', operation #2. In this case, we just:
5. Cancel operation #2.
Rather than track previous location and update it, we simply mark the operation as ‘cancelled’.
Oops, now we want to redo that operation!
6. Cancel operation #5.
Now the cancellation is cancelled. Operation #2 is now live once again.
Consider a user ‘A' dragging a slider for ‘x' from 0, through 1, then releasing on 2. What events occur for user 'A’ and 'B’?
The property system describes a tree structure, with properties as the leaves of the tree, very much like a file system of folders and files. Unlike a file system, there is some (optional) implied meaning behind the paths. Within the Cavrnus implementation we call this the Context of the property path.
This document describes why this exists, how it is useful, and how it is implemented.
Properties is a generic system. Properties can be created of any property type at any path.
However, properties generally are grouped by their purpose. This purpose provides useful information when navigating the properties tree. Furthermore there are occasions where this purpose is necessary to completely resolve a property value (this is rare, but relevant!). This purpose, we encapsulate in an object called IPropertyContainerContext. Within all PropertySetManager, the node object of the property system, there is a collection of these contexts, accessible to integration code.
The property system does not ever set up these contexts; they are run-time only objects, attached generally during initialization. There is no protocol or serialization for these types. As such they are generally application specific.
There is also a second aspect to these contexts. As of Cavrnus 2022.2.1, the Unity application establishes a set of read-only properties, in all nodes with contexts, that provide property-level information about the context.
Let’s have an example:
When the unity application receives an OpCreateObject operation, it begins the process of loading the object. It decides on the property path for the object given the object’s ObjectId, using PropertyDefs::ObjectContainer. It then adds a context to that node, which in the case of the Unity app, provides access to the runtime object that manages that object. This allows property related extensions to access application-specific information. This is never accessed generically using properties; it is always specific to the integration.
Secondly, it writes the well-known, read-only, identifier properties into the property tree, this consists of:
The context’s type (in this case the string ‘object’)
The expected types here are available at PropertyDefs.PropertyContext_Type_*
The context’s specific type (in this case, the type of the object; for example, ‘video’)
Likewise, at PropertyDefs.PropertyContext_ObjectType_*
Usually, a name.
Most loaded Holo components generate their own node and context, identifying their type alongside their properties.
All of the above definition and data doesn’t actually do anything. But it supports secondary systems, in particular, Holoscripting!
Holoscripting generally works at a very low level within the Cavrnus system; it works with the property tree directly rather than with any application specific information. All of this metadata provides Holoscripting methods to understand, search, and navigate the scene. If Holoscripting wishes to turn off all the lights, it can search for light contexts and manipulate their properties. The search is only possible using the context provided data.
In 2022.2.3 we introduced a new type of property value. This property is a pointer to a node in the property tree. It provides a way to point, generically, to an asset or other loaded content.
The first property to use this system is /room/skybox. Previously this was a string property that contained the contentId for an object stored on the server. After 2022.2.3 this property is a link property to a loaded asset. The context of this target is used to find the runtime texture needed to render the skybox. This extra level of indirection is not terribly useful for skyboxes, but is a substantial component of the subsequent update to the materials system, in 2022.2.4 or soon afterwards.
Alongside this change of property data, there are new OpCreateObject types. In particular, a new option for ObjectType, ‘ContentIdAsset’, which is used to load an asset (texture, until materials and other systems leverage this feature), into the room. This is different than just-'ContentId', which would take an image asset and place it on a board. These assets are set up with a property context and information, then wait for some other system to require them, at which point they are downloaded, initialized, cached, and provided to the consumer.
The most basic update for /room/skybox would require handling the ‘ContentIdAsset’ loaded textures, retaining at a minimum the objectId for that asset (which is used to establish the property path and context), as well as traversing the value of a link property to its context, and then to the actual asset to be used.
This same system will be used shortly for materials and textures.
You might see the following operation sequence:
Sub-properties are assigned automatically based on the type of the property. Some properties have explicitly added sub-properties. Sub-properties can be referenced by expressions the same as the root properties.
Sub-properties are not computed unless some expression or script is dependent upon it. Their value updates immediately after their source property updates.
Sub-properties are meant to be interpretations of the source value, though this is not a hard constraint.
As an example, if you wish to set up an expression that incorporates the current day of the month, you could use the following reference:
/date.day
This accesses the ‘/date’ property’s ‘day’ sub-property.
This document describes the built-in sub-properties available first by property type, then by property, for those that have additional assigned.
None are established at this time.
.x, .y, .z, .w
Number sub-fields which extract the components of the Vector.
.r, .b, .g, .a
Number sub-fields which extract the components of the Color.
.not
Presents the negation of the boolean property value.
None are established at this time.
Json properties set up sub-properties to reflect the contents of the json value. If the property value was:
{ a: 10, b: 20, c: { nest: 30 } }
Then there will be sub-properties .a, .b, .c. a and b will be scalar values, while c will also be a Json property type. In fact, this sub-property can further be sub-propertied: referencing .c.nest is valid.
.pos
A vector sub-property that presents the translational component of the Transform
.rot
A vector sub-property that presents the euler-rotation component of the Transform. Note this does not account for quaternion rotations whatsoever, but does include lookat rotations.
.scaling
A scalar-valued sub-property that presents the overall scale factor of the transform. In the case of uniform scales this value will be correct, but in nonuniform scaling transforms, it will be computed as the determinant of the final transform, or as the cube root of the multiplication of the independent axes' scales.
.scale
A vector-valued sub-property that presents the scaling of the transform, per axis.
.forward
A vector-valued directional vector that presents the forward orientation of the transform. Forward, for an identity transform, will be the positive Z-Axis.
.up
A vector-valued directional vector representing the up-vector. By default, the Y-Axis.
.right
A vector-valued directional vector representing the right-vector. By default, the X-Axis.
The following sub-properties are included only with the Unity client, and not set up by the SDK systems alone:
.worldUp, .worldForward, .worldRight, .worldEuler, .worldQuat, .worldScale, .worldPos
These are vector-valued sub-properties that present the relevant values transformed into world coordinates.
/date
.isoday
The current date in yyyy-MM-dd form, as a string.
.year
The number of the current year.
.month
The number of the current month.
.day
The number of the day of the month
.dow
The name of the current day of the week.
.hour
The number of the current hour, from 0 to 23.
.minute
.second
.millisecond
The millisecond field does not update every millisecond and will be quite a bit more coarse.
The properties system includes a method for computing numerical derivatives of scalar, vector, and color properties. These derivates can be computed with respect to any scalar-valued dependency. These derivatives will not take changes to property assignments into account; they can only account for animated property generators.
As an example, consider a fictitious scalar property /room/brightness, set to an expression Math.cos(ref('t')). This property will animate as a cosine wave over time.
The derivative of this scalar property can be referenced using the id /room/brightness.dd|time. This value will follow a sin curve, in this instance.
The general form of the sub-property is .dd followed by the property id, encoded, of the variable with which to derivate with respect to. The property id is encoded by replacing its / with |. .dd|time thereby means the derivative with respect to real time.
Derivatives are computed numerically by reevaluating values using an offset for the ‘time’ value. The offset value is quite large to avoid numerical instability.
The primary use of this system is to understand playback speed for animations, videos, sound clips, etc, but it a general purpose system and available for use.
Almost all property types allow the user to assign an Expression to compute the value. Expressions allow assigning complicated functions, which can depend on the values of other properties, or be animated and change over time.
Expressions are interpreted as javascript code, and have a set of automatically available types ready for use. The intrinsic javascript Math namespace is available, as well as the types included in Holoscripting Definitions: Math Types. The Holoscripting runtime '@cavrnus/runtime' is not available.
1 + Math.cos(0)
“Object A"
`Object ${somevariable}`
{ x:0, y:10, z:5 }
{ r:0, g:1, b:.5, a:.8 }
true
0 === 1
{data:Transform.lookat(Float3.new(5,5,0),Float3.new(0,1,0))}
{ something: 'is this', wat : 2 }
Available only in expressions, is the function ref(propertyId : string). The ref function will resolve other property values, and yield an appropriate type. Scalar property deferencing yields a number, etc.
Expressions search for properties relative to the property on which they are assigned. If the expression is assigned on the property /room/skyboxIntensity, which has a sibling property /room/skyboxRotation, the following expression for the former is valid:
10 + ref('skyboxRotation')
Equivalently a non-relative, absolute, path can be used if needed:
10 + ref('/room/skyboxRotation')
References are tracked, and will ensure that the expression based property will update whenever the dependent property value changes. In the above case, this means that whenever skyboxRotation changes, skyboxIntensity would immediately be recomputed, as well.
Also note that references can resolve sub-properties. See the sub-properties document to understand what is available for use.
The referenced property can also be a reference! Let’s build an example:
Let’s create a new string property selectedObject, which will contain the propertyId path of a selected object (e.g. /objects/objectidhere) Let’s define our expression to be normally 10, and if the selected object is visible, 50.
10 + ref(`${ref('selectedObject')}/vis`) ? 40 : 0
Breaking this down, first let’s resolve the internal reference:
`${ref('selectedObject')}.vis` => “/objects/objectidhere" + "/vis" => “/objects/objectidhere/vis"
This fetches the visible boolean of the selected object, which is a boolean property.
In this expression, the produced value will be updated whenever either selectedObject changes, OR when the current selectedObject’s vis changes.
Dynamic references can only be nested once. ref(ref(ref('arg'))) will show warnings when assigned, and always produce the default value for the property type. If anyone reading this document finds a use for triply nested dereferencing, please let us know precisely why.
There are two methods for incorporating time into an expression.
Expressions provide a ‘fake’ property to reference, called t. t always means ‘time since this expression was applied’. t will be synchronized among all participants in the space. If a user rejoins the space after a day, t will still be ‘seconds since this expression was applied’, which will be a large large value.
As a simple example, to set a simple occilating scalar value:
10 + Math.cos(ref('t'))
Or to assign a rotating vector:
Float3.fromPolar(ref('t'), 0, 2)
The /time property also exists. time is defined to be ‘seconds since the room was joined’. This value will be different for all users and is not synchronized. The time value should probably be not directly consumed. It primarily exists in order to handle synchronizing t.
If you wish to synchronize multiple animations, it is recommended to create a new property to reflect this new time. This animation-time can be dependent upon t, or use a built-in playback generator type to make it easy. Then each animation can be dependent upon animation-time instead.
For example:
import { out} from '@cavrnus/runtime';
out.warn(`Something is wrong!`);
Types are defined as they are using Typescript notation.
‘out’ defines a set of output functions, useful for showing warnings and errors, or writing to the log or other consoles.
All message types will be written to the log, in additional to their listed behavior.
log(msg : any)
Write a message to the log file.
print(msg : any)
Write a message to the application console (available at the moment only in internal and staging builds, using F7)
debug(msg : any)
Write a message to the application console (available at the moment only in internal and staging builds, using F7)
warn(msg : any)
Write a message to the console, and, in internal and staging builds, present a dialog to the user with the contents of the warning.
warning(msg : any)
Alias for warn()
error(msg : any)
Write a message to the console, and present a dialog to the user with the contents of the message, regardless of build type.
verbose(msg : any)
No additional behavior.
The scripting and expression systems both provide access to a number of important underlying types and functions.
From Holoscripting, these types are defined within the ‘@cavrnus/runtime’ module, and the function collections must be imported from this module.
For example:
import { Float3 } from '@cavrnus/runtime';
const vector = Float3.new(1,2,3);
While writing expressions, no imports are possible. All of the types contained within this document are already imported and available for use.
For example:
Float3.new(Math.sin(Math.PI), 2, 4);
Also note, the Math namespace intrinsic to javascript is fully available.
Types are defined as they are using Typescript notation. In general, types are defined a simple interface, with a separate class of ‘static’ functions to operate or construct those interfaces. User code can construct an object to implement the interfaces itself if it wishes to.
A Float3T is a mathematical vector type, defined as Float3T, as:
export interface Float3T{x: number; y: number; z: number;}
Float3 contains the following functions.
Float3.new(x : number, y : number, z : number) : Float3T
Constructs a new Float3T. Equivalent to {x, y, z};
Float3.add(a : Float3T, b : Float3T) : Float3T
Adds the vectors, component-wise.
Float3.subtract(a : Float3T, b : Float3T) : Float3T
Subtracts the vectors, component-wise.
Float3.dot(a : Float3T, b : Float3T) : number
Computes the dot product, the sum of component-wise multiplication.
Float3.cross(a : Float3T, b : Float3T) : Float3T
Computes the cross product, yielding a vector perpendicular to both the inputs, with magnitude based on the product of the magnitude of the original vectors, and the sin of the angle between them.
Float3.scale(a : Float3T, b : number | Float3T) : Float3T
Scales/Multiplies the vector a by either a constant number b, or component-wise by a vector b.
Float3.rotate(a : Float3T, axis : Float3T, radians : number) : Float3T
Rotates the vector a around the axis axis, an angle of radians radians.
Float3.angleBetween(a : Float3T, b : Float3T) : number
Computes the angle between the vectors a and b, using the inverse cos of their normalized dot product.
Float3.fromPolar(theta : number, psi: number, dist : number) : Float3T
Computes a Float3 vector from the given angles theta (rotation around the up-vector/y-axis), and psi (tilt, around right-vector/x-axis), with magnitude dist.
= { x : -Math.sin(theta) * Math.cos(psi) * dist, y : Math.sin(psi) * dist, z : Math.cos(theta) * Math.cos(psi) * dist) }
Float3.magnitude(a : Float3T) : number
Computes the length of the vector a.
Float3.magnitude2(a : Float3T) : number
Computes the square of the length of the vector a.
Float3.toString(a : Float3T) : string
Produces a standard format string of the vector a.
A Float4T is a mathematical vector type, defined as Float4T, as:
export interface Float4T{x: number; y: number; z: number; w: number;}
Float4T thereby implicitly implements Float3T and can be used in any Float3 functions. 4-component vectors are used commonly when interacting with scripting and properties, but generally 3d vector math works off of 3-component vectors. Float4 accordingly has noticeably fewer functions.
Note you can build a Float4T from a Float3T concisely using the spread operator: {...f3, w:1}
Float4 contains the following functions:
Float4.new(x : number, y : number, z : number, w: number) : Float4T
Constructs a new Float3T. Equivalent to {x, y, z, w};
Float4.add(a : Float4T, b : Float4T) : Float4T
Adds the vectors, component-wise.
Float4.subtract(a : Float4T, b : Float4T) : Float4T
Subtracts the vectors, component-wise.
ColorT represents an RGBA color, and is used frequently in the properties and materials systems. Unlike Float3T and Float4T colors treat the fourth component (alpha) differently than the vector w component. It is defined as:
export interface ColorT{r: number; g: number; b: number; a: number;}
Color.new(r : number, g : number, b : number, a? : number) : ColorT
Trivial constructor, equivalent to:
{ r, g, b, a : a ?? 1 }
Color.parse(hexstring : string) : ColorT
Interprets a color definition string with 3, 4, or 8 hex characters. Leading # or 0x are ignored.
Color.add(a : ColorT, b : ColorT) : ColorT
Component-wise sum.
Color.subtract(a : ColorT, b : ColorT) : ColorT
Component-wise subtraction
Color.scale(a : ColorT, b : number | ColorT) : ColorT
If b is a number, scale the rgb components of a only.
If b is a ColorT, component-wise multiplication.
Color.hsv(hue : number, sat : number, val : number, alpha? : number) : ColorT
Computes a new color given hue (range 0 to 360), saturation (0 to 1), and value (0 to 1), and optionally included alpha, which defaults to 1.
Rand is a collection of random value generators.
Rand.random(a : number, b? : number) : number
If two args, generate a random number between a and b.
If one arg, generate a random number between 0 and a.
Rand.randomf(a : number, b? : number ) : number
Alias for random. f for float.
Rand.randomi(a : number, b? : number) : number
Generate a random integer from a to b, inclusively.
If a single arg, 0 to a, inclusively.
Rand.randomb() : boolean
Random boolean. Coin flip.
Rand.randomDisc(radius : number) : Float3T
Generate a random vector in the XZ plane, within radius distance of the origin. Y will = 0.
Rand.randomAnnulus(inner : number, outer : number) : Float3T
Like randomDisc, but with a minimum distance inner from the origin as well.
Rand.randomSphere(radius : number) : Float3T
Generate a random position within a radius radius sphere, of the origin.
Rand.randomShell(radius : number) : Float3T
Generate a random position on the surface of a sphere, of radius radius.
Rand.randomCube(radius : number) : Float3T
Generate a random position within a cube with each dimension radius.
Rand.randomGaussian(mean : number, stddev : number) : number
Generate a normally-distributed value, given mean mean, and standard deviation stddev.
Rand.randomColor(alpha? : number) : ColorT
Generate a random color with given alpha. RGB will be generated with Rand.randomf(0,1)
A Shape represents a simple geometric shape. This type is used primarily to set up intersection tests. Shape is defined as a union type SphereT | AABBT, defined subsequently.
Geometry contains a few functions to compute information on Shapes.
Geometry.distance(a : Shape, b : Shape) : number
Computes the minimum spanning distance between the two Shapes.
Geometry.distanceBetween(a : Shape, b : Shape) : number
An alias for distance.
Represents a Sphere, generally used for bounds testing.
export interface SphereT{center: Float3T; radius: number;}
Sphere.new(center : Float3T, radius : number) : SphereT
Trivial constructor, equivalent to { center, radius };
Represents an Axis-Aligned Bounding Box, generally used for bounds testing.
export interface AABBT{min: Float3T;max: Float3T;}
AABB.new(min : Float3T, max: Float3T) : AABBT
Trivial constructor, equivalent to: { min, max }
Transforms represent a position, orientation, and often scale, of an object. Transforms are an intrinsic type of the properties system, and are used for every object loaded into Cavrnus. Rather than represent transforms by a 3 by 4 matrix, Cavrnus represents a transform with a generic component-based system, so that each component can leverage other features of the properties system, such as expressions and cross referencing.
This document describes the utility types and functions that represent a transform from within Holoscripting. The same system describes the internal usage within the SDK, but the types will be named slightly differently.
From Holoscripting, these types are defined within the ‘@cavrnus/runtime’ module, and the function collections must be imported from this module.
For example:
import { Transform } from '@cavrnus/runtime';
Transform.srt(...);
Types are defined as they are using Typescript notation. In general, types are defined a simple interface, with a separate class of ‘static’ functions to operate or construct those interfaces. User code can construct an object to implement the interfaces itself if it wishes to.
export interface TransformCompleteT{data : TransformDataT;updates : TransformUpdateT[];}
TransformCompleteT represents the complete state of a transform. A transform being in world-space or in local-space depends on the property usage, and cannot be inferred from the transform value by itself.
data : TransformDataT
Is the defining value for a transform. It will be either an SRT, SQT, or LookAt based transform data, defined below.
updates : TransformUpdateT[]
Updates are processes added to the transform after beginning with the data component. Updates do not present to the UI and are intended to be temporary overlay effects, such a small pulse to gather attention to an object.
Transform contains a set of type-narrowing functions to determine the nature of a TransformDataT, or a TransformUpdateT.
It also contains constructor functions for those types. Rather than detail each function we will just include the prototypes here:
srt(scale : Float3T, euler : Float3T, translation : Float3T) : TransformDataSRT;
sqt(scale : Float3T, quat : Float4T, translation : Float3T) : TransformDataSQT;
lookat(eye : Float3T, lookAt : Float3T, nominalUp? : Float3T | undefined) : TransformDataLookAt;
isSrt(data : TransformDataT) : data is TransformDataSRT;6isSqt(data : TransformDataT) : data is TransformDataSQT;
isLookAt(data : TransformDataT) : data is TransformDataLookAt;
shiftUpdate(moveBy : Float3T) : TransformUpdateShift;
lookAtUpdate(rotateToFacePt : Float3T, percentageToMove : number) : TransformUpdateLookAt;
scaleUpdate(scale : number) : TransformUpdateScaleUniform;
scaleNonuniformUpdate(scale : Float3T) : TransformUpdateScaleNonuniform;
rotateEulerUpdate(euler : Float3T) : TransformUpdateRotateEuler;
rotateQuatUpdate(quat : Float4T) : TransformUpdateRotateQuat;
toEulerUpdate(rotateToEuler : Float3T, percentageToMove : number) : TransformUpdateToEuler;
toQuatUpdate(rotateToQuat : Float4T, percentageToMove : number) : TransformUpdateToQuat;
isShiftUpdate(data : TransformUpdateT) : data is TransformUpdateShift;
isLookAtUpdate(data : TransformUpdateT) : data is TransformUpdateLookAt;
isScaleUpdate(data : TransformUpdateT) : data is TransformUpdateScaleUniform;
isScaleNonuniformUpdate(data : TransformUpdateT) : data is TransformUpdateScaleNonuniform;
isRotateEulerUpdate(data : TransformUpdateT) : data is TransformUpdateRotateEuler;
isRotateQuatUpdate(data : TransformUpdateT) : data is TransformUpdateRotateQuat;
isToEulerUpdate(data : TransformUpdateT) : data is TransformUpdateToEuler;
isToQuatUpdate(data : TransformUpdateT) : data is TransformUpdateToQuat;
export type TransformDataT = TransformDataSRT | TransformDataSQT | TransformDataLookAt;
To narrow the type of a TransformDataT use the is* functions of Transform, defined above.
export interface TransformDataSRT{scale : Float3T; euler : Float3T; translation : Float3T;}
export interface TransformDataSQT{scale : Float3T; quat : Float4T; translation : Float3T;}
export interface TransformDataLookAt{ eye : Float3T; lookAt : Float3T; nominalUp? : Float3T;}
export type TransformUpdateT = TransformUpdateShift | TransformUpdateLookAt | TransformUpdateScaleUniform | TransformUpdateScaleNonuniform | TransformUpdateRotateEuler | TransformUpdateRotateQuat | TransformUpdateToEuler | TransformUpdateToQuat;
Offsets the transform position without adjusting orientation.
export interface TransformUpdateShift{moveBy : Float3T;}
Partially rotate a transform to reorient it toward the given point.
export interface TransformUpdateLookAt{rotateToFacePt : Float3T; percentageToMove : Float3;}
Scale up a transform uniformly. Any changes to position updated after this will end up multiplied.
export interface TransformUpdateScaleUniform{scale : number;}
Scale up a transform non-uniformly, by each axis. This can produce skews in some circumstances, which may have unexpected side-effects.
export interface TransformUpdateScaleNonuniform{scale : Float3T;}
Incrementally rotate a transform, using euler angles.
export interface TransformUpdateRotateEuler{euler : Float3T;}
Incrementally rotate a transform, using a quaternion
export interface TransformUpdateRotateQuat{quat : Float4T;}
Partially rotate a transform to orient with the given euler angles.
export interface TransformUpdateToEuler{rotateToEuler : Float3T; percentageToMove : number;}
Partially rotate a transform to orient with the given quaternion based rotation.
export interface TransformUpdateToQuat{rotateToQuat : Float4T; percentageToMove : number;}
This document describes the purpose and functionality of the root level function:
beginOperations(options? : OpContextOptions) : OpContext.
This function is the primary entry point used to enact changes to the state of the space. Before reading this further, it is recommended to get a basic understanding of the Journal, Operations, and Transients.
beginOperations constructs an object the script may use to invoke transients and operations within the space. In general this is the only method to do so. All updates will go through this object. The function prototype is as follows:
- beginOperations(options? : OpContextOptions) : OpContext
The OpContext is the object by which changes can be made. An OpContext can be constructed at almost any time within script, and its lifecycle can continue beyond the constructing function. Once given context is either .commit()ed, or .cancel()ed, it should be considered destroyed and discarded. Further use will throw exceptions.
op(operation : Operation, opOptions? : OpOptions) : void
Op is an extremely powerful and generic function, used to submit any type of operation.
When called this function will immediately send a transient version of the operation.
This function provides extremely low level and powerful access to sending operations, but because of that it is quite complicated! It is our recommendation that this function only ever be called as a last resort. After reading the Properties section below, the reasoning should be more clear.
Three properties of the OpContext provide the utility needed to post operations without using .op(). They each provide a subset of functionality specific to different types of operations.
prop : OpContextProperties
create : OpContextCreations
user : OpContextUser
user provides functions specific to updating user control properties. These are transient-only properties that make it possible to move the user’s view, and control their avatar. Take care when doing so not to make your users nauseous.
This options object provides some controls governing an OpContext. These options will apply to all operations posted to the OpContext through .op() or any child methods.
sendTransients? : boolean
undoable? : boolean
individualUndo? : boolean
This options object can be supplied to .op(), or any child-property function that submits an operation.
transientUniqueId? : string
bypassLocalApplication? : boolean
This document describes the purpose and functionality of the prop property of the OpContext, retrieved through .beginOperations().
This object is used to update property value generators and declare new properties. Like all components of OpContext (this, OpContextCreations, and OpContextUser, at the time of writing), all functions contained here eventually make their changes via OpContext.op(). All of these functions are mere helpers! But they are very helpful helpers, so do use them. :)
It would be best to understand the basics of properties before reading through the remainder of this document.
Unlike cav.prop.declareProperty(), properties defined using an operation are permanently stored in the space’s journal. There is no mechanism for removing this definition; though it can be altered afterwards.
WIP