-
Notifications
You must be signed in to change notification settings - Fork 40
Open
Description
I have modified Mouse3Dmanager.hx & MouseEvent3D.hx based on the as code here to incorporate the missing MouseEvent3D events...
MouseEvent.MIDDLE_CLICK, onMouseMiddleClick
MouseEvent.MIDDLE_MOUSE_UP, onMouseMiddleUp
MouseEvent.MIDDLE_MOUSE_DOWN, onMouseMiddleDown
MouseEvent.RIGHT_CLICK, onMouseRightClick
MouseEvent.RIGHT_MOUSE_UP, onMouseRightUp
MouseEvent.RIGHT_MOUSE_DOWN, onMouseRightDown
Mouse3DManager.hx
package away3d.core.managers;
import away3d.containers.ObjectContainer3D;
import away3d.containers.View3D;
import away3d.core.pick.IPicker;
import away3d.core.pick.PickingCollisionVO;
import away3d.core.pick.PickingType;
import away3d.events.MouseEvent3D;
import openfl.display.DisplayObject;
import openfl.display.DisplayObjectContainer;
import openfl.display.Stage;
import openfl.events.MouseEvent;
import openfl.geom.Vector3D;
import openfl.Vector;
/**
* Mouse3DManager enforces a singleton pattern and is not intended to be instanced.
* it provides a manager class for detecting 3D mouse hits on View3D objects and sending out 3D mouse events.
*/
class Mouse3DManager
{
public var forceMouseMove(get, set):Bool;
public var mousePicker(get, set):IPicker;
private static var _view3Ds:Map<View3D, Int>;
private static var _view3DLookup:Vector<View3D>;
private static var _viewCount:Int = 0;
private var _activeView:View3D;
private var _updateDirty:Bool = true;
private var _nullVector:Vector3D = new Vector3D();
private static var _collidingObject:PickingCollisionVO;
private static var _previousCollidingObject:PickingCollisionVO;
private static var _collidingViewObjects:Vector<PickingCollisionVO>;
private static var _queuedEvents:Vector<MouseEvent3D> = new Vector<MouseEvent3D>();
private var _mouseMoveEvent:MouseEvent = new MouseEvent(MouseEvent.MOUSE_MOVE);
private static var _mouseUp:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_UP);
private static var _mouseClick:MouseEvent3D = new MouseEvent3D(MouseEvent3D.CLICK);
private static var _mouseOut:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_OUT);
private static var _mouseDown:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_DOWN);
private static var _mouseMove:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_MOVE);
private static var _mouseOver:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_OVER);
private static var _mouseWheel:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MOUSE_WHEEL);
private static var _mouseDoubleClick:MouseEvent3D = new MouseEvent3D(MouseEvent3D.DOUBLE_CLICK);
private static var _mouseMiddleClick:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MIDDLE_CLICK);
private static var _mouseMiddleUp:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MIDDLE_MOUSE_UP);
private static var _mouseMiddleDown:MouseEvent3D = new MouseEvent3D(MouseEvent3D.MIDDLE_MOUSE_DOWN);
private static var _mouseRightClick:MouseEvent3D = new MouseEvent3D(MouseEvent3D.RIGHT_CLICK);
private static var _mouseRightUp:MouseEvent3D = new MouseEvent3D(MouseEvent3D.RIGHT_MOUSE_UP);
private static var _mouseRightDown:MouseEvent3D = new MouseEvent3D(MouseEvent3D.RIGHT_MOUSE_DOWN);
private var _forceMouseMove:Bool;
private var _mousePicker:IPicker = PickingType.RAYCAST_FIRST_ENCOUNTERED;
private var _childDepth:Int = 0;
private static var _previousCollidingView:Int = -1;
private static var _collidingView:Int = -1;
private var _collidingDownObject:PickingCollisionVO;
private var _collidingUpObject:PickingCollisionVO;
/**
* Creates a new <code>Mouse3DManager</code> object.
*/
public function new()
{
if (_view3Ds == null) {
_view3Ds = new Map<View3D, Int>();
_view3DLookup = new Vector<View3D>();
}
}
// ---------------------------------------------------------------------
// Interface.
// ---------------------------------------------------------------------
public function updateCollider(view:View3D):Void
{
_previousCollidingView = _collidingView;
if (view != null) {
// Clear the current colliding objects for multiple views if backBuffer just cleared
if (view.stage3DProxy.bufferClear)
_collidingViewObjects = new Vector<PickingCollisionVO>(_viewCount);
if (!view.shareContext) {
if (view == _activeView && (_forceMouseMove || _updateDirty)) { // If forceMouseMove is off, and no 2D mouse events dirtied the update, don't update either.
_collidingObject = _mousePicker.getViewCollision(view.mouseX, view.mouseY, view);
}
} else {
if (view.getBounds(view.parent).contains(view.mouseX + view.x, view.mouseY + view.y)) {
if (_collidingViewObjects == null)
_collidingViewObjects = new Vector<PickingCollisionVO>(_viewCount);
_collidingObject = _collidingViewObjects[_view3Ds[view]] = _mousePicker.getViewCollision(view.mouseX, view.mouseY, view);
}
}
}
}
public function fireMouseEvents():Void
{
var i:Int = 0;
var len:Int;
var event:MouseEvent3D;
var dispatcher:ObjectContainer3D;
// If multiple view are used, determine the best hit based on the depth intersection.
if (_collidingViewObjects != null) {
_collidingObject = null;
// Get the top-most view colliding object
var distance:Float = Math.POSITIVE_INFINITY;
var view:View3D;
var v:Int = _viewCount - 1;
while (v >= 0) {
view = _view3DLookup[v];
if (_collidingViewObjects[v] != null && (view.layeredView || _collidingViewObjects[v].rayEntryDistance < distance)) {
distance = _collidingViewObjects[v].rayEntryDistance;
_collidingObject = _collidingViewObjects[v];
if (view.layeredView)
break;
}
v--;
}
}
// If colliding object has changed, queue over/out events.
if (_collidingObject != _previousCollidingObject) {
if (_previousCollidingObject != null)
queueDispatch(_mouseOut, _mouseMoveEvent, _previousCollidingObject);
if (_collidingObject != null)
queueDispatch(_mouseOver, _mouseMoveEvent, _collidingObject);
}
// Fire mouse move events here if forceMouseMove is on.
if (_forceMouseMove && _collidingObject != null)
queueDispatch(_mouseMove, _mouseMoveEvent, _collidingObject);
// Dispatch all queued events.
len = _queuedEvents.length;
for (i in 0...len) {
// Only dispatch from first implicitly enabled object ( one that is not a child of a mouseChildren = false hierarchy ).
event = _queuedEvents[i];
dispatcher = event.object;
while (dispatcher != null && !dispatcher._ancestorsAllowMouseEnabled)
dispatcher = dispatcher.parent;
if (dispatcher != null)
dispatcher.dispatchEvent(event);
}
_queuedEvents.length = 0;
_updateDirty = false;
_previousCollidingObject = _collidingObject;
}
public function addViewLayer(view:View3D):Void
{
var stg:Stage = view.stage;
// Add instance to mouse3dmanager to fire mouse events for multiple views
if (view.stage3DProxy.mouse3DManager == null)
view.stage3DProxy.mouse3DManager = this;
if (!hasKey(view))
_view3Ds.set(view, 0);
_childDepth = 0;
traverseDisplayObjects(stg);
_viewCount = _childDepth;
}
public function enableMouseListeners(view:View3D):Void
{
view.addEventListener(MouseEvent.CLICK, onClick);
view.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick);
view.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
view.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
view.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
view.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
view.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
view.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
view.addEventListener(MouseEvent.MIDDLE_CLICK, onMouseMiddleClick);
view.addEventListener(MouseEvent.MIDDLE_MOUSE_UP, onMouseMiddleUp);
view.addEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, onMouseMiddleDown);
view.addEventListener(MouseEvent.RIGHT_CLICK, onMouseRightClick);
view.addEventListener(MouseEvent.RIGHT_MOUSE_UP, onMouseRightUp);
view.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, onMouseRightDown);
}
public function disableMouseListeners(view:View3D):Void
{
view.removeEventListener(MouseEvent.CLICK, onClick);
view.removeEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick);
view.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
view.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
view.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
view.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
view.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
view.removeEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
view.removeEventListener(MouseEvent.MIDDLE_CLICK, onMouseMiddleClick);
view.removeEventListener(MouseEvent.MIDDLE_MOUSE_UP, onMouseMiddleUp);
view.removeEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, onMouseMiddleDown);
view.removeEventListener(MouseEvent.RIGHT_CLICK, onMouseRightClick);
view.removeEventListener(MouseEvent.RIGHT_MOUSE_UP, onMouseRightUp);
view.removeEventListener(MouseEvent.RIGHT_MOUSE_DOWN, onMouseRightDown);
}
public function dispose():Void
{
_mousePicker.dispose();
}
// ---------------------------------------------------------------------
// Private.
// ---------------------------------------------------------------------
private function queueDispatch(event:MouseEvent3D, sourceEvent:MouseEvent, collider:PickingCollisionVO = null):Void
{
// 2D properties.
event.ctrlKey = sourceEvent.ctrlKey;
event.altKey = sourceEvent.altKey;
event.shiftKey = sourceEvent.shiftKey;
event.delta = sourceEvent.delta;
event.screenX = sourceEvent.localX;
event.screenY = sourceEvent.localY;
if (collider == null)
collider = _collidingObject;
// 3D properties.
if (collider != null) {
// Object.
event.object = collider.entity;
event.renderable = collider.renderable;
// UV.
event.uv = collider.uv;
// Position.
event.localPosition = (collider.localPosition != null) ? collider.localPosition.clone() : null;
// Normal.
event.localNormal = (collider.localNormal != null) ? collider.localNormal.clone() : null;
// Face index.
event.index = collider.index;
// SubGeometryIndex.
event.subGeometryIndex = collider.subGeometryIndex;
} else {
// Set all to null.
event.uv = null;
event.object = null;
event.localPosition = _nullVector;
event.localNormal = _nullVector;
event.index = 0;
event.subGeometryIndex = 0;
}
// Store event to be dispatched later.
_queuedEvents.push(event);
}
private function reThrowEvent(event:MouseEvent):Void
{
if (_activeView == null || (_activeView != null && !_activeView.shareContext))
return;
var keys:Iterator<View3D> = _view3Ds.keys();
for (v in keys) {
if (v != _activeView && _view3Ds.get(v) == _view3Ds.get(_activeView) - 1) {
if (event.bubbles == true)
v.dispatchEvent(new MouseEvent(event.type, false, event.cancelable, event.localX, event.localY, event.relatedObject, event.ctrlKey, event.altKey, event.shiftKey, event.buttonDown, event.delta, event.commandKey, event.clickCount));
else v.dispatchEvent(event);
}
}
}
private function hasKey(view:View3D):Bool
{
return _view3Ds.exists(view);
}
private function traverseDisplayObjects(container:DisplayObjectContainer):Void
{
var childCount:Int = container.numChildren;
var c:Int = 0;
var child:DisplayObject;
for (c in 0...childCount) {
child = container.getChildAt(c);
if (Std.is(child, View3D) && _view3Ds.exists(cast child)) {
_view3Ds[cast child] = _childDepth;
_view3DLookup[_childDepth] = cast child;
_childDepth++;
}
if (Std.is(child, DisplayObjectContainer)){
traverseDisplayObjects(cast(child, DisplayObjectContainer));
}
}
}
// ---------------------------------------------------------------------
// Listeners.
// ---------------------------------------------------------------------
private function onMouseMove(event:MouseEvent):Void
{
if (_collidingObject != null) {
queueDispatch(_mouseMove, _mouseMoveEvent = event);
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseOut(event:MouseEvent):Void
{
_activeView = null;
if (_collidingObject != null) {
queueDispatch(_mouseOut, event, _collidingObject);
}
_updateDirty = true;
}
private function onMouseOver(event:MouseEvent):Void
{
_activeView = cast(event.currentTarget, View3D);
if (_collidingObject != null && _previousCollidingObject != _collidingObject) {
queueDispatch(_mouseOver, event, _collidingObject);
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onClick(event:MouseEvent):Void
{
if (_collidingObject != null) {
queueDispatch(_mouseClick, event);
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onDoubleClick(event:MouseEvent):Void
{
if (_collidingObject != null) {
queueDispatch(_mouseDoubleClick, event);
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseDown(event:MouseEvent):Void
{
_activeView = cast(event.currentTarget, View3D);
updateCollider(_activeView); // ensures collision check is done with correct mouse coordinates on mobile
if (_collidingObject != null) {
queueDispatch(_mouseDown, event);
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseUp(event:MouseEvent):Void
{
if (_collidingObject != null) {
queueDispatch(_mouseUp, event);
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseWheel(event:MouseEvent):Void
{
if (_collidingObject != null){
queueDispatch(_mouseWheel, event);
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseMiddleClick(event:MouseEvent ):Void
{
if (_collidingObject != null){
queueDispatch(_mouseMiddleClick, event );
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseMiddleUp(event:MouseEvent ):Void
{
if (_collidingObject != null){
queueDispatch( _mouseMiddleUp, event );
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseMiddleDown(event:MouseEvent ):Void
{
_activeView = cast(event.currentTarget, View3D);
updateCollider(_activeView); // ensures collision check is done with correct mouse coordinates on mobile
if (_collidingObject != null) {
queueDispatch( _mouseMiddleDown, event );
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseRightClick( event:MouseEvent ):Void
{
if (_collidingObject != null){
queueDispatch( _mouseRightClick, event );
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseRightUp( event:MouseEvent ):Void
{
if (_collidingObject != null){
queueDispatch( _mouseRightUp, event );
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
private function onMouseRightDown( event:MouseEvent ):Void
{
_activeView = cast(event.currentTarget, View3D);
updateCollider(_activeView); // ensures collision check is done with correct mouse coordinates on mobile
if (_collidingObject != null) {
queueDispatch( _mouseRightDown, event );
_previousCollidingObject = _collidingObject;
} else {
reThrowEvent(event);
}
_updateDirty = true;
}
// ---------------------------------------------------------------------
// Getters & setters.
// ---------------------------------------------------------------------
private function get_forceMouseMove():Bool
{
return _forceMouseMove;
}
private function set_forceMouseMove(value:Bool):Bool
{
_forceMouseMove = value;
return value;
}
private function get_mousePicker():IPicker
{
return _mousePicker;
}
private function set_mousePicker(value:IPicker):IPicker
{
_mousePicker = value;
return value;
}
}
MouseEvent3D.hx
package away3d.events;
import away3d.containers.ObjectContainer3D;
import away3d.containers.View3D;
import away3d.core.base.IRenderable;
import away3d.core.math.Matrix3DUtils;
import away3d.materials.MaterialBase;
import openfl.events.Event;
import openfl.geom.Point;
import openfl.geom.Vector3D;
/**
* A MouseEvent3D is dispatched when a mouse event occurs over a mouseEnabled object in View3D.
* todo: we don't have screenZ data, tho this should be easy to implement
*/
class MouseEvent3D extends Event
{
public var scenePosition(get, never):Vector3D;
public var sceneNormal(get, never):Vector3D;
// Private.
@:allow(away3d) private var _allowedToPropagate:Bool = true;
@:allow(away3d) private var _parentEvent:MouseEvent3D;
/**
* Defines the value of the type property of a mouseOver3d event object.
*/
public static inline var MOUSE_OVER:String = "mouseOver3d";
/**
* Defines the value of the type property of a mouseOut3d event object.
*/
public static inline var MOUSE_OUT:String = "mouseOut3d";
/**
* Defines the value of the type property of a mouseUp3d event object.
*/
public static inline var MOUSE_UP:String = "mouseUp3d";
/**
* Defines the value of the type property of a mouseDown3d event object.
*/
public static inline var MOUSE_DOWN:String = "mouseDown3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var MOUSE_MOVE:String = "mouseMove3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var MIDDLE_CLICK:String = "middleMouseClick3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var MIDDLE_MOUSE_UP:String = "middleMouseUp3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var MIDDLE_MOUSE_DOWN:String = "middleMouseDown3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var RIGHT_CLICK:String = "mouseRightClick3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var RIGHT_MOUSE_UP:String = "mouseRightUp3d";
/**
* Defines the value of the type property of a mouseMove3d event object.
*/
public static inline var RIGHT_MOUSE_DOWN:String = "mouseRightDown3d";
/**
* Defines the value of the type property of a rollOver3d event object.
*/
// public static const ROLL_OVER : String = "rollOver3d";
/**
* Defines the value of the type property of a rollOut3d event object.
*/
// public static const ROLL_OUT : String = "rollOut3d";
/**
* Defines the value of the type property of a click3d event object.
*/
public static inline var CLICK:String = "click3d";
/**
* Defines the value of the type property of a doubleClick3d event object.
*/
public static inline var DOUBLE_CLICK:String = "doubleClick3d";
/**
* Defines the value of the type property of a mouseWheel3d event object.
*/
public static inline var MOUSE_WHEEL:String = "mouseWheel3d";
/**
* The horizontal coordinate at which the event occurred in view coordinates.
*/
public var screenX:Float;
/**
* The vertical coordinate at which the event occurred in view coordinates.
*/
public var screenY:Float;
/**
* The view object inside which the event took place.
*/
public var view:View3D;
/**
* The 3d object inside which the event took place.
*/
public var object:ObjectContainer3D;
/**
* The renderable inside which the event took place.
*/
public var renderable:IRenderable;
/**
* The material of the 3d element inside which the event took place.
*/
public var material:MaterialBase;
/**
* The uv coordinate inside the draw primitive where the event took place.
*/
public var uv:Point;
/**
* The index of the face where the event took place.
*/
public var index:Int;
/**
* The index of the subGeometry where the event took place.
*/
public var subGeometryIndex:Int;
/**
* The position in object space where the event took place
*/
public var localPosition:Vector3D;
/**
* The normal in object space where the event took place
*/
public var localNormal:Vector3D;
/**
* Indicates whether the Control key is active (true) or inactive (false).
*/
public var ctrlKey:Bool;
/**
* Indicates whether the Alt key is active (true) or inactive (false).
*/
public var altKey:Bool;
/**
* Indicates whether the Shift key is active (true) or inactive (false).
*/
public var shiftKey:Bool;
/**
* Indicates how many lines should be scrolled for each unit the user rotates the mouse wheel.
*/
public var delta:Int;
/**
* Create a new MouseEvent3D object.
* @param type The type of the MouseEvent3D.
*/
public function new(type:String)
{
super(type, true, true);
}
/**
* @inheritDoc
*/
#if flash
@:getter(bubbles) function get_bubbles():Bool
{
var doesBubble:Bool = super.bubbles && _allowedToPropagate;
_allowedToPropagate = true;
// Don't bubble if propagation has been stopped.
return doesBubble;
}
#end
/**
* @inheritDoc
*/
override public function stopPropagation():Void
{
super.stopPropagation();
_allowedToPropagate = false;
if (_parentEvent != null)
_parentEvent.stopPropagation();
}
/**
* @inheritDoc
*/
override public function stopImmediatePropagation():Void
{
super.stopImmediatePropagation();
_allowedToPropagate = false;
if (_parentEvent != null)
_parentEvent.stopImmediatePropagation();
}
/**
* Creates a copy of the MouseEvent3D object and sets the value of each property to match that of the original.
*/
override public function clone():Event
{
var result:MouseEvent3D = new MouseEvent3D(type);
#if flash
if (isDefaultPrevented())
result.preventDefault();
#end
result.screenX = screenX;
result.screenY = screenY;
result.view = view;
result.object = object;
result.renderable = renderable;
result.material = material;
result.uv = uv;
result.localPosition = localPosition;
result.localNormal = localNormal;
result.index = index;
result.subGeometryIndex = subGeometryIndex;
result.delta = delta;
result.ctrlKey = ctrlKey;
result.shiftKey = shiftKey;
result._parentEvent = this;
result._allowedToPropagate = _allowedToPropagate;
return result;
}
/**
* The position in scene space where the event took place
*/
private function get_scenePosition():Vector3D
{
if (Std.is(object, ObjectContainer3D))
return Matrix3DUtils.transformVector(cast(object, ObjectContainer3D).sceneTransform,localPosition);
else
return localPosition;
}
/**
* The position in scene space where the event took place
* @param v destination Vector3D
* @return
*/
public function getScenePosition(v:Vector3D = null):Vector3D {
if(v == null) v = new Vector3D();
if (Std.is(object, ObjectContainer3D)) {
Matrix3DUtils.transformVector(cast(object, ObjectContainer3D).sceneTransform,localPosition,v);
}else{
v.x = localPosition.x;
v.y = localPosition.y;
v.z = localPosition.z;
}
return v;
}
/**
* The normal in scene space where the event took place
*/
private function get_sceneNormal():Vector3D
{
if (Std.is(object, ObjectContainer3D)) {
var sceneNormal:Vector3D = Matrix3DUtils.deltaTransformVector(cast(object, ObjectContainer3D).sceneTransform,localNormal);
sceneNormal.normalize();
return sceneNormal;
} else
return localNormal;
}
/**
* The normal in scene space where the event took place
* @param v destination Vector3D
* @return
*/
public function getSceneNormal(v:Vector3D = null):Vector3D {
if(v == null) v = new Vector3D();
if (Std.is(object, ObjectContainer3D)) {
Matrix3DUtils.deltaTransformVector(cast(object, ObjectContainer3D).sceneTransform,localNormal, v);
v.normalize();
} else {
v.x = localNormal.x;
v.y = localNormal.y;
v.z = localNormal.z;
}
return v;
}
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels