@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
411 lines (402 loc) • 11.6 kB
text/typescript
import type {Ref} from '@vue/reactivity';
import {Intersection, Material, Object3D} from 'three';
import {ConvertToStrings} from '../../../../../types/GlobalTypes';
import {MouseButton, MouseButtons} from '../../../../../core/MouseButton';
import {TypeAssert} from '../../../../poly/Assert';
export interface PriorityOptions {
blockObjectsBehind: boolean;
skipIfObjectsInFront: boolean;
}
export interface CPUOptions {
traverseChildren: boolean;
pointsThreshold: number;
lineThreshold: number;
intersectionRef: Ref<Intersection | null>;
}
export interface GPUOptions {
// if a worldPosMaterial is given, we use it and only need a single render call.
// if not, we render need 2 renders, 1 to write to the depth buffer and another to read it (in addition to readRenderTargetPixels)
worldPosMaterial: Material | null;
distanceRef: Ref<number>;
}
export interface ObjectOptions {
priority: PriorityOptions;
cpu?: CPUOptions;
gpu?: GPUOptions;
}
export interface EmptyOptions {}
export function hasCPUOptions(optionsList: ObjectOptions[]): boolean {
for (const options of optionsList) {
if (options.cpu != null) {
return true;
}
}
return false;
}
export function hasGPUOptions(optionsList: ObjectOptions[]): boolean {
for (const options of optionsList) {
if (options.gpu != null) {
return true;
}
}
return false;
}
export function GPUOptionsDepthBufferRequired(optionsList: ObjectOptions[]): boolean {
for (const options of optionsList) {
if (options.gpu != null && options.gpu.worldPosMaterial == null) {
return true;
}
}
return false;
}
function _cpuOptionsEqual(options1: CPUOptions, options2: CPUOptions): boolean {
return (
options1.traverseChildren == options2.traverseChildren &&
options1.pointsThreshold == options2.pointsThreshold &&
options1.lineThreshold == options2.lineThreshold
);
}
export function CPUOptionsEqual(optionsList: ObjectOptions[]): boolean {
let firstCPUOptions: CPUOptions | undefined;
for (const options of optionsList) {
if (options.cpu != null) {
if (firstCPUOptions == null) {
firstCPUOptions = options.cpu;
} else {
if (!_cpuOptionsEqual(firstCPUOptions, options.cpu)) {
return false;
}
}
}
}
return true;
}
export function CPUOptionsMax(optionsList: ObjectOptions[], target: CPUOptions): CPUOptions {
target.traverseChildren = false;
target.pointsThreshold = -1;
target.lineThreshold = -1;
for (const options of optionsList) {
if (options.cpu != null) {
if (target.traverseChildren == false && options.cpu.traverseChildren == true) {
target.traverseChildren = options.cpu.traverseChildren;
}
if (target.pointsThreshold < options.cpu.pointsThreshold) {
target.pointsThreshold = options.cpu.pointsThreshold;
}
if (target.lineThreshold < options.cpu.lineThreshold) {
target.lineThreshold = options.cpu.lineThreshold;
}
}
}
return target;
}
export enum Status {
REQUIRED = 0, //'required',
OPTIONAL = 1, //'optional',
FORBIDDEN = 2, //'forbidden',
}
export const STATUS_OPTIONS: Status[] = [Status.REQUIRED, Status.OPTIONAL, Status.FORBIDDEN];
export const STATUS_OPTION_LABEL: string[] = ['required', 'optional', 'forbidden'];
export const DEFAULT_STATUS_OPTION = STATUS_OPTIONS.indexOf(Status.OPTIONAL);
export const STATUS_MENU_OPTIONS = {
menu: {
entries: STATUS_OPTIONS.map((value) => ({
value,
name: STATUS_OPTION_LABEL[value],
})),
},
};
export interface ButtonOptions {
left: boolean;
middle: boolean;
right: boolean;
}
export interface ButtonsOptions {
left: Status;
middle: Status;
right: Status;
}
export interface ModifierOptions {
ctrl: Status;
shift: Status;
alt: Status;
}
// export interface ModifierIndexOptions {
// ctrl: number;
// shift: number;
// alt: number;
// }
// export function modifierIndexToModifierOptions(options: ModifierIndexOptions): ModifierOptions {
// return {
// ctrl: POINTER_EVENT_MODIFIER_OPTIONS[options.ctrl],
// shift: POINTER_EVENT_MODIFIER_OPTIONS[options.shift],
// alt: POINTER_EVENT_MODIFIER_OPTIONS[options.alt],
// };
// }
export interface ButtonAndModifierOptions {
button: ButtonOptions;
modifier: ModifierOptions;
}
export interface ButtonsAndModifierOptions {
button: ButtonsOptions;
modifier: ModifierOptions;
}
export interface OnlyModifierOptions {
modifier: ModifierOptions;
}
// export interface ButtonAndModifierIndexOptions {
// button: ButtonOptions;
// modifier: ModifierIndexOptions;
// }
export interface ButtonAndModifierOptionsAsString {
button: ConvertToStrings<ButtonOptions>;
modifier: ConvertToStrings<ModifierOptions>;
}
export interface ButtonsAndModifierOptionsAsString {
button: ConvertToStrings<ButtonsOptions>;
modifier: ConvertToStrings<ModifierOptions>;
}
export interface ModifierOptionsAsString {
modifier: ConvertToStrings<ModifierOptions>;
}
interface PropertyWithButtonConfig {
config: ButtonAndModifierOptions;
}
interface PropertyWithButtonsConfig {
config: ButtonsAndModifierOptions;
}
interface PropertyWithModifierConfig {
config: OnlyModifierOptions;
}
export interface ButtonConfig {
button: MouseButton;
ctrl: boolean;
shift: boolean;
alt: boolean;
}
export interface ButtonsConfig {
buttons: MouseButtons;
ctrl: boolean;
shift: boolean;
alt: boolean;
}
function statusMatch(modifierProperty: Status, value: boolean): boolean {
switch (modifierProperty) {
case Status.REQUIRED: {
return value == true;
}
case Status.OPTIONAL: {
return true;
}
case Status.FORBIDDEN: {
return value == false;
}
}
TypeAssert.unreachable(modifierProperty);
}
export function propertyMatchesModifiersConfig(
propertyConfig: OnlyModifierOptions,
buttonsConfig: ButtonsConfig | ButtonConfig
) {
return (
statusMatch(propertyConfig.modifier.ctrl, buttonsConfig.ctrl) &&
statusMatch(propertyConfig.modifier.shift, buttonsConfig.shift) &&
statusMatch(propertyConfig.modifier.alt, buttonsConfig.alt)
);
}
export function propertyMatchesButtonConfig(propertyConfig: ButtonAndModifierOptions, buttonConfig: ButtonConfig) {
switch (buttonConfig.button) {
case MouseButton.LEFT: {
if (propertyConfig.button.left == false) {
return false;
}
break;
}
case MouseButton.MIDDLE: {
if (propertyConfig.button.middle == false) {
return false;
}
break;
}
case MouseButton.RIGHT: {
if (propertyConfig.button.right == false) {
return false;
}
break;
}
}
return propertyMatchesModifiersConfig(propertyConfig, buttonConfig);
}
export function propertyMatchesButtonsConfig(propertyConfig: ButtonsAndModifierOptions, buttonsConfig: ButtonsConfig) {
switch (buttonsConfig.buttons) {
case MouseButtons.LEFT: {
if (
propertyConfig.button.left == Status.FORBIDDEN ||
propertyConfig.button.middle == Status.REQUIRED ||
propertyConfig.button.right == Status.REQUIRED
) {
return false;
}
break;
}
case MouseButtons.MIDDLE: {
if (
propertyConfig.button.left == Status.REQUIRED ||
propertyConfig.button.middle == Status.FORBIDDEN ||
propertyConfig.button.right == Status.REQUIRED
) {
return false;
}
break;
}
case MouseButtons.RIGHT: {
if (
propertyConfig.button.left == Status.REQUIRED ||
propertyConfig.button.middle == Status.REQUIRED ||
propertyConfig.button.right == Status.FORBIDDEN
) {
return false;
}
break;
}
case MouseButtons.LEFT_RIGHT: {
if (
propertyConfig.button.left == Status.FORBIDDEN ||
propertyConfig.button.middle == Status.REQUIRED ||
propertyConfig.button.right == Status.FORBIDDEN
) {
return false;
}
break;
}
case MouseButtons.LEFT_MIDDLE: {
if (
propertyConfig.button.left == Status.FORBIDDEN ||
propertyConfig.button.middle == Status.FORBIDDEN ||
propertyConfig.button.right == Status.REQUIRED
) {
return false;
}
break;
}
case MouseButtons.MIDDLE_RIGHT: {
if (
propertyConfig.button.left == Status.REQUIRED ||
propertyConfig.button.middle == Status.FORBIDDEN ||
propertyConfig.button.right == Status.FORBIDDEN
) {
return false;
}
break;
}
case MouseButtons.LEFT_MIDDLE_RIGHT: {
if (
propertyConfig.button.left == Status.FORBIDDEN ||
propertyConfig.button.middle == Status.FORBIDDEN ||
propertyConfig.button.right == Status.FORBIDDEN
) {
return false;
}
break;
}
}
return propertyMatchesModifiersConfig(propertyConfig, buttonsConfig);
}
function propertiesMatchesButtonConfig(
propertiesList: PropertyWithButtonConfig[],
buttonConfig: ButtonConfig
): boolean {
for (const properties of propertiesList) {
if (propertyMatchesButtonConfig(properties.config, buttonConfig)) {
return true;
}
}
return false;
}
function propertiesMatchesButtonsConfig(
propertiesList: PropertyWithButtonsConfig[],
buttonConfig: ButtonsConfig
): boolean {
for (const properties of propertiesList) {
if (propertyMatchesButtonsConfig(properties.config, buttonConfig)) {
return true;
}
}
return false;
}
function propertiesMatchesModifiersConfig(
propertiesList: PropertyWithModifierConfig[],
buttonConfig: ButtonsConfig
): boolean {
for (const properties of propertiesList) {
if (propertyMatchesModifiersConfig(properties.config, buttonConfig)) {
return true;
}
}
return false;
}
export function buttonConfigFromEvent(event: Readonly<PointerEvent | MouseEvent | TouchEvent>, target: ButtonConfig) {
target.button = (event as PointerEvent).button || MouseButton.LEFT;
target.ctrl = event.ctrlKey;
target.shift = event.shiftKey;
target.alt = event.altKey;
}
export function buttonsConfigFromEvent(event: Readonly<PointerEvent | MouseEvent | TouchEvent>, target: ButtonsConfig) {
target.buttons = (event as MouseEvent).buttons || MouseButtons.LEFT;
target.ctrl = event.ctrlKey;
target.shift = event.shiftKey;
target.alt = event.altKey;
}
const _buttonConfig: ButtonConfig = {button: MouseButton.LEFT, ctrl: false, shift: false, alt: false};
const _buttonsConfig: ButtonsConfig = {buttons: MouseButtons.LEFT, ctrl: false, shift: false, alt: false};
export function filterObjectsWithMatchButtonConfig(
event: Readonly<PointerEvent | MouseEvent | TouchEvent>,
objects: Object3D[],
propertiesListByObject: Map<Object3D, PropertyWithButtonConfig[]>,
target: Object3D[]
) {
target.length = 0;
buttonConfigFromEvent(event, _buttonConfig);
for (const object of objects) {
const propertiesList = propertiesListByObject.get(object);
if (propertiesList) {
if (propertiesMatchesButtonConfig(propertiesList, _buttonConfig)) {
target.push(object);
}
}
}
}
export function filterObjectsWithMatchButtonsConfig(
event: Readonly<PointerEvent | MouseEvent | TouchEvent>,
objects: Object3D[],
propertiesListByObject: Map<Object3D, PropertyWithButtonsConfig[]>,
target: Object3D[]
) {
target.length = 0;
buttonsConfigFromEvent(event, _buttonsConfig);
for (const object of objects) {
const propertiesList = propertiesListByObject.get(object);
if (propertiesList) {
if (propertiesMatchesButtonsConfig(propertiesList, _buttonsConfig)) {
target.push(object);
}
}
}
}
export function filterObjectsWithMatchModifiersConfig(
event: Readonly<PointerEvent | MouseEvent | TouchEvent>,
objects: Object3D[],
propertiesListByObject: Map<Object3D, PropertyWithModifierConfig[]>,
target: Object3D[]
) {
target.length = 0;
buttonsConfigFromEvent(event, _buttonsConfig);
for (const object of objects) {
const propertiesList = propertiesListByObject.get(object);
if (propertiesList) {
if (propertiesMatchesModifiersConfig(propertiesList, _buttonsConfig)) {
target.push(object);
}
}
}
}