SceneObject

Properties

  • name (string)
  • id (string) — Unique Id
  • clusterMemberCount (integer) — How many members are in this object's overlap cluster this frame (see ' Renderer.getOverlapClusters()) - 1 means solo (the common case, and the only ' value possible when Renderer.computeOverlapClusters is false). Set once per frame ' by Renderer, right after it computes clusters - not meant to be set directly by ' game code.
  • drawable (Drawable)
  • type (SceneObjectType)
  • negDistanceFromCamera (float) — The negative distance from the camera, used for depth sorting
  • worldPosition (dynamic)
  • transformationMatrix (dynamic) — The Current Transformation Matrix
  • lastFrameWasCulled (boolean) — Whether the last frame's draw was skipped because the frustum rejected this object, ' or because a subsequent draw attempt failed deterministically (see ' isDeterministicDrawFailure()). Both latch: nothing moved, so re-running the check ' would give the same answer, and skipping it is what makes a static off-screen object ' free. A draw that was attempted and failed for a reason that could change without ' anything moving (e.g. a transient scratch-bitmap-pool exhaustion) must not latch - ' findCanvasPosition() and performDraw() both already retry on the following frame, ' and treating that kind of failure as a cull is what made one bad frame permanent. ' See issue #48 (the original cull-latch fix) and issue #73 (extending it to ' deterministic draw failures, like a backface or an off-canvas rejection, which used ' to re-run the full frustum check every single frame with no latch at all).
  • lastFrameDidDraw (boolean) — Whether the last frame's draw actually happened. Distinct from lastFrameWasCulled ' because a frame has four possible outcomes, not two: drew, was culled by the ' frustum, entered the draw path and failed deterministically, or entered the draw ' path and failed transiently. Only the first kind of failure may latch (folded into ' lastFrameWasCulled); a transient failure must retry; and reaching a failure at all is ' what makes the frustum check reachable, which is what puts an object into the culled ' state in the first place.
  • hasValidWorldPosition (boolean)
  • hasValidCanvasPosition (boolean)
  • wasEnabledLastFrame (boolean)
  • isFirstFrameSinceEnabled (boolean)
  • isLowEndDevice (boolean)
  • lastGeometryVersion (integer) — The drawable's geometryVersion as of the last completed draw - see geometryChanged()
  • lastDrawMode (integer) — The draw mode this object last drew in - see drawModeChanged()
  • lastProjectionVersion (integer) — The camera's projectionVersion as of the last draw - see projectionChanged()
  • depthChangedThisFrame (boolean) — True for exactly the frame this object's negDistanceFromCamera was recomputed - ' Renderer.updateSceneObjects() ORs these together to decide whether the whole ' scene needs a re-sort this frame, instead of always resorting even when nothing ' that could change draw order actually happened.
  • stableSortKey (float) — What Renderer actually sorts by - negDistanceFromCamera quantized into ' DEPTH_TIE_EPSILON-wide buckets, combined with this object's sort position from ' the previous frame, so two objects whose depths quantize into the same bucket ' keep their previous relative order instead of swapping from floating-point jitter ' alone. This is bucket quantization, not a true epsilon-tolerance comparison - ' two depths closer together than the epsilon can still straddle a bucket boundary ' and swap order. A genuine depth crossover still swaps order correctly, since the ' quantized depth term dominates the combined key.

Constructor

new SceneObject( name: string, drawableObj: Drawable, objType: SceneObjectType, ): SceneObject

Parameters


Instance Methods

setId(id: string): void

Parameters

  • id (string)

Returns

  • void

setLastSortIndex(index: integer): void

Parameters

  • index (integer)

Returns

  • void

getLastSortIndex(): integer

Returns

  • integer

update(cameraObj: Camera): void

Update the scene object for the current frame. Updates the world position and checks if the object should be drawn. Updates negDistanceFromCamera for depth sorting. Do not override this function.

Parameters

Returns

  • void

getPositionForCameraDistance( drawMode: SceneObjectDrawMode, ): BGE.Math.Vector

Get the position of the object for the camera distance calculation Override this function in subclasses to provide specific behavior

Parameters

Returns

  • BGE.Math.Vector

isEnabled(): boolean

Check if the object is enabled

Returns

  • boolean — true if this sceneObject should be drawn or updated

updateWorldPosition(drawMode: SceneObjectDrawMode): boolean

protected

Update the world position of the object Override this function in subclasses to provide specific behavior

Parameters

Returns

  • boolean

draw(rendererObj: Renderer): void

Draw the object with the current renderer Calculates if the object is on screen and if it should be drawn. Do not override this function!

Parameters

Returns

  • void

drawModeChanged(drawMode: SceneObjectDrawMode): boolean

protected

Whether this object is drawing in a different draw mode than it last drew in.

Changing draw mode has to force a recompute, because the modes don't want the same geometry as each other - directScaled wants a quad built facing the camera, while the oriented modes want the object's own quad projected. World and canvas geometry are otherwise only recomputed when the object or camera moves, so without this a stationary object switched between modes would keep drawing from geometry computed for the mode it was in before, and (since nothing is moving) would never recover.

Parameters

Returns

  • boolean — true if it differs from the last drawn mode

geometryChanged(): boolean

protected

Whether the drawable changed shape since this object last drew. Movement is dirty-checked via MotionChecker, but a change in shape - a resized rectangle, a polygon's points being replaced - isn't movement, so it's declared explicitly by Drawable.invalidateGeometry() and picked up here. Tracked as a version number rather than a flag so that one Drawable registered with more than one Renderer (the game canvas and the UI canvas, say) can't have one of its SceneObjects clear the flag before the other has seen it.

Returns

  • boolean — true if the drawable's geometry changed since the last draw

projectionChanged(cameraObj: Camera): boolean

protected

Whether the camera's projection - its frame size or field of view - changed since this object last drew. Neither counts as camera movement, so the motion dirty-check can't see them, and both change where a world point lands on the canvas and what the frustum accepts. Tracked as a version number rather than a flag for the same reason geometryVersion is: one camera serves every SceneObject in its renderer, so no single object may clear it.

Parameters

Returns

  • boolean — true if the camera's projection changed since the last draw

findCanvasPosition( rendererObj: Renderer, drawMode: SceneObjectDrawMode, ): boolean

protected

Updates any properties that specify the final canvas position of the object Override this function in subclasses to provide specific behavior

Parameters

Returns

  • boolean — True if there is a valid canvas position for the final draw call

performDraw( rendererObj: Renderer, drawMode: SceneObjectDrawMode, ): boolean

protected

Perform any drawing logic for the object Override this function in subclasses to provide specific behavior

Parameters

Returns

  • boolean — True if the draw call was successful

getPrimitiveCount(): integer

How many separately-orderable draw primitives this object currently has. The base-class default (1) is correct for every billboard-family SceneObject - a quad/circle/text/etc. is always drawn as one piece regardless of draw mode. SceneObjectModel overrides this to return its actual per-face count, which does vary with draw mode (backface-culled modes only count front-facing faces; the DrawBackFace variants count both).

Returns

  • integer

getPrimitiveDepth(index: integer): float

The depth to sort primitive index by, within a multi-member cluster's combined primitive list. The base-class default (this object's own overall depth) is correct for the single-primitive case.

Contract: the returned value must be on the same convention as negDistanceFromCamera - negative in front of the camera, and ascending sort = farthest-first (matching the main scene's own painter's-algorithm sort convention). Every override (e.g. SceneObjectModel's per-face depth) must convert to this same convention, or primitives from different SceneObject subtypes will sort against each other backwards within a shared cluster.

Parameters

  • index (integer)

Returns

  • float

drawPrimitive(rendererObj: Renderer, index: integer): boolean

Draws primitive index now. The base-class default delegates to this object's normal whole-object draw path (performDraw, with its normal caching behavior intact) - correct for the single-primitive case, where "draw primitive 0" and "draw the whole object" are the same operation. SceneObjectModel overrides this to draw one face directly (bypassing its whole-model temp-bitmap cache, which can't represent cross-object interleaving).

Parameters

  • rendererObj (Renderer)
  • index (integer)

Returns

  • boolean — whether this primitive actually drew something

afterDraw(): void

protected

Handle any logic after the object has been drawn Override this function in subclasses to provide specific behavior

Returns

  • void

objMovedInRelationToCamera(cameraObj: Camera): boolean

protected

Check if the object has moved since it was last drawn

Parameters

Returns

  • boolean

isPotentiallyOnScreen(cameraObj: Camera): boolean

protected

Check if the object is potentially on screen.

Repeats last frame's answer for free when nothing has changed, and otherwise runs the real frustum check. The frustum check is deliberately reachable from the "entered the draw path and failed" state as well as from movement: it is what puts an object into the culled state at all, and what lets a failed object discover it is still on screen and retry. Latching on any non-draw - which is what the old framesSinceDrawn counter did - is what made a single failed frame permanent. See issue #48.

Parameters

Returns

  • boolean

getPositionsForFrustumCheck( drawMode: SceneObjectDrawMode, ): dynamic

protected

Get the bounding positions of the object for frustum checks

This single-point default is also what BGE.DepthSort's overlap clustering (via getBoundingPoints() below) sees for any subclass that doesn't override this - currently SceneObjectPlane and SceneObjectParallaxLayer. A single point can't form a real convex hull (BGE.DepthSort.MIN_VALID_HULL_POINTS requires 3), so both types are excluded from clustering entirely rather than participating in it - a known, disclosed limitation (see #59 follow-ups), not a silent gap: they fail closed (never falsely cluster) rather than fail open (falsely cluster with anything whose AABB happens to contain that one point).

Parameters

Returns

  • dynamic

getBoundingPoints(cameraObj: Camera): dynamic

Public wrapper around getPositionsForFrustumCheck(), for cross-object callers (e.g. BGE.DepthSort's overlap detection) that need this object's own bounding points but aren't a SceneObject subclass themselves.

Parameters

Returns

  • dynamic

participatesInOverlapDetection(): boolean

Whether this object is worth including in overlap-cluster detection (BGE.DepthSort/Renderer.getOverlapClusters()) at all. The base-class default (true) is correct for anything with real area/volume, which the narrow phase (BGE.DepthSort.hullsOverlap) can actually test via a >=3-point convex hull. SceneObjectLine overrides this to false: a line's own bounding points (getPositionsForFrustumCheck) are always exactly 2 (its two endpoints), so its projected hull can never reach MIN_VALID_HULL_POINTS - hullsOverlap() already rejects it via isValidHull() unconditionally, meaning a line could never actually be unioned into a real cluster with anything, before or after this method existed. This isn't tolerating a small visual error - the scenario ("two lines clustered in the wrong order") is structurally impossible under the current hull-based narrow phase. What this method actually buys is skipping the broad-phase setup cost (screen-bounds projection, hull construction, sort/sweep entries) for objects that were always going to fail the narrow phase anyway - this matters in practice for a scene built from many line segments (e.g. examples/3d's TreesRoom, each tree a bundle of DrawableLine branches), which would otherwise dominate the cluster-candidate count for zero possible benefit.

Returns

  • boolean

isDeterministicDrawFailure( rendererObj: Renderer, drawMode: SceneObjectDrawMode, ): boolean

protected

Whether performDraw() just returning false is a failure that will keep happening every frame until something moves or the geometry changes - and so is safe to latch exactly like a frustum cull (see lastFrameWasCulled) - as opposed to a transient failure (e.g. a scratch bitmap checkout failing) that must keep retrying. Only called immediately after performDraw() returns false.

Override this when a subclass has a cheap, already-necessary check that explains a draw failure this precisely - e.g. SceneObjectBillboard overrides it for a backface cull and for a canvas position that's within the frustum but entirely off-canvas (see issue #73). The default assumes any failure could be transient, which is always safe - it just means this subclass's draw failures keep retrying every frame, same as before this existed.

Parameters

Returns

  • boolean

isCulled(): boolean

Whether this object was culled (skipped entirely) on the last frame drawn - either by the camera frustum, or by a deterministic draw failure (see isDeterministicDrawFailure()) that's being treated the same way. Game code can use this (e.g. via GameEntity.isOnScreen()/Drawable.isOnScreen()) to skip its own expensive per-frame work for an entity that isn't currently visible - see issue #75.

Returns

  • boolean