有没有办法在 ThreeJs 中使用 dat.gui.js 构建范围滑块?

Is there a way to build range slider with dat.gui.js in ThreeJs?

我想在我的设计中有一个范围滑块,虽然我有一个数字滑块,但我想要 select 一个要传递的数字范围,如下图所示。

除了在 dat.gui.js 文件中进行更改并添加范围滑块功能之外,还有其他选择吗?

不,那是不可能的。控制器的创建完全基于 value-type 其中 is handled here。您将不得不破解它,制作一个 pull-request 或只使用两个滑块 (min/max)。

我已经在 React 中实现了一个 dat-gui 范围滑块(使用 material-ui)。也许这会对尝试创建自己的人有所帮助,所以我想我会 post 在这里。 Here's what the slider looks like

import React, { useEffect, useRef, useCallback } from 'react';
import cx from 'classnames';
import { makeStyles, Theme, createStyles } from '@material-ui/core';
import clamp from 'lodash/clamp';
import useEventCallback from 'utils/useEventCallback';

function asc(a: number, b: number) {
    return a - b;
}

function findClosest(values: any, currentValue: number) {
    const { index: closestIndex } = values.reduce((acc: { distance: number; } | null, value: number, index: number) => {
        const distance = Math.abs(currentValue - value);

        if (acc === null || distance < acc.distance || distance === acc.distance) {
            return {
                distance,
                index,
            };
        }

        return acc;
    }, null);
    return closestIndex;
}

function valueToPercent(value: number, min: number, max: number) {
    return ((value - min) * 100) / (max - min);
}

function percentToValue(percent: number, min: number, max: number) {
    return (max - min) * percent + min;
}

function getDecimalPrecision(num: number) {
    // This handles the case when num is very small (0.00000001), js will turn this into 1e-8.
    // When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.
    if (Math.abs(num) < 1) {
        const parts = num.toExponential().split('e-');
        const matissaDecimalPart = parts[0].split('.')[1];
        return (
            (matissaDecimalPart ? matissaDecimalPart.length : 0) +
            parseInt(parts[1], 10)
        );
    }

    const decimalPart = num.toString().split('.')[1];
    return decimalPart ? decimalPart.length : 0;
}

function roundValueToStep(value: number, step: number) {
    const nearest = Math.round(value / step) * step;
    return Number(nearest.toFixed(getDecimalPrecision(step)));
}

function setValueIndex({ values, source, newValue, index }: any) {
    // Performance shortcut
    if (values[index] === newValue) {
        return source;
    }

    const output = [...values];
    output[index] = newValue;
    return output;
}

const axisProps = {
    offset: (percent: number) => ({ left: `${percent}%` }),
    leap: (percent: number) => ({ width: `${percent}%` }),
};

const trackMouse = (event: React.MouseEvent) => ({ x: event.clientX, y: event.clientY, });

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        root: {
            width: '100%',
            boxSizing: 'border-box',
            display: 'inline-block',
            cursor: 'ew-resize',
            touchAction: 'none',
            border: '3px solid #1a1a1a',
        },
        slider: {
            display: 'block',
            position: 'relative',
            backgroundColor: '#141414',
            backgroundImage: `linear-gradient(90deg, ${theme.palette.primary.light}, ${theme.palette.primary.light})`, //#2fa1d6, #2fa1d6)",
            backgroundRepeat: 'no-repeat',
            height: '14px',
        },
    })
)

interface Props {
    values: number[];
    min: number;
    max: number;
    step: number;
    className?: string;
    color?: string;
    defaultValue?: number[];
    disabled?: boolean;
    onChange?: (event: React.MouseEvent, value: any) => void;
    onChangeCommitted?: (event: React.MouseEvent, value: any) => void;
    onMouseDown?: (event: React.MouseEvent) => void;
}
export default function RangeSlider({
    className,
    color = 'primary',
    defaultValue,
    disabled = false,
    max,
    min,
    onChange,
    onChangeCommitted,
    onMouseDown,
    step,
    values: valuesProp,
    ...other
}: Props) {
    const classes = useStyles();
    const sliderRef = useRef<any>();
    const previousIndex = useRef<any>();

    let values = [...valuesProp].sort(asc);
    values = values.map((value: number) => clamp(value, min, max));

    const getNewValue = useCallback(
        ({ mouse, move = false, values: values2, source }) => {

            const { current: slider } = sliderRef;
            const { width, left } = slider.getBoundingClientRect();
            const percent = (mouse.x - left) / width;

            let newValue;
            newValue = percentToValue(percent, min, max);
            newValue = roundValueToStep(newValue, step);
            newValue = clamp(newValue, min, max);
            let activeIndex = 0;

            if (!move) {
                activeIndex = findClosest(values2, newValue);
            } else {
                activeIndex = previousIndex.current;
            }

            const previousValue = newValue;
            newValue = setValueIndex({
                values: values2,
                source,
                newValue,
                index: activeIndex,
            }).sort(asc);
            activeIndex = newValue.indexOf(previousValue);
            previousIndex.current = activeIndex;


            return { newValue, activeIndex };


        },
        [max, min, step]
    );

    const handleMouseMove = useEventCallback((event: React.MouseEvent) => {
        const mouse = trackMouse(event);

        const { newValue } = getNewValue({
            mouse,
            move: true,
            values,
            source: valuesProp,
        });

        if (onChange) {
            onChange(event, newValue);
        }
    });

    const handleMouseEnter = useEventCallback((event: React.MouseEvent) => {
        // If the slider was being interacted with but the mouse went off the window
        // and then re-entered while unclicked then end the interaction.
        if (event.buttons === 0) {
            handleMouseEnd(event);
        }
    });

    const handleMouseEnd = useEventCallback((event: React.MouseEvent) => {
        const mouse = trackMouse(event);

        const { newValue } = getNewValue({
            mouse,
            values,
            source: valuesProp,
        });

        if (onChangeCommitted) {
            onChangeCommitted(event, newValue);
        }

        window.removeEventListener('mousemove', handleMouseMove);
        window.removeEventListener('mouseup', handleMouseEnd);
        window.removeEventListener('mouseenter', handleMouseEnter);
    });

    useEffect(() => {
        return () => {
            window.removeEventListener('mousemove', handleMouseMove);
            window.removeEventListener('mouseup', handleMouseEnd);
            window.removeEventListener('mouseenter', handleMouseEnter);
        };
    }, [disabled, handleMouseEnter, handleMouseEnd, handleMouseMove]);

    const handleMouseDown = useEventCallback((event: React.MouseEvent) => {
        if (onMouseDown) {
            onMouseDown(event);
        }

        if (disabled) {
            return;
        }

        event.preventDefault();
        const mouse = trackMouse(event);
        const { newValue } = getNewValue({
            mouse,
            values,
            source: valuesProp,
        });

        if (onChange) {
            onChange(event, newValue);
        }

        window.addEventListener('mousemove', handleMouseMove);
        window.addEventListener('mouseenter', handleMouseEnter);
        window.addEventListener('mouseup', handleMouseEnd);
    });

    const sliderOffset = valueToPercent(values[0], min, max);
    const sliderLeap = valueToPercent(values[values.length - 1], min, max) - sliderOffset;
    const widthBackground = axisProps.leap(sliderLeap).width;

    const sliderStyle = {
        ...axisProps.offset(sliderOffset),
        ...axisProps.leap(sliderLeap),
        backgroundSize: `${widthBackground}% 100%`,
    };

    return (
        <span
            ref={sliderRef}
            className={cx(classes.root, className)}
            onMouseDown={handleMouseDown}
            {...other}
        >
            <span className={classes.slider} style={sliderStyle} />
            {values.map((value, index) => {
                const percent = valueToPercent(value, min, max);
                const style = axisProps.offset(percent);
                return (
                    <span
                        key={index}
                        role="slider"
                        style={style}
                        data-index={index}
                    />
                );
            })}
        </span>
    );
}

RangeSlider.defaultProps = {
    min: 0,
    max: 100,
    step: 1
}